Artem Goncharov 63fe02bf77 Support uploading image from data and stdin
It might be useful to upload image from stdin. Disadvantage is, that it
is not possible to calculate checksums and this will prohibit threaded
image upload when using swift and tasks.
Additionally fix checksum validation during image creation - when
create_image is not called through cloud layer - the result might be
different, when checksums of existing image match (due to call and
return cloud.get_image). Fixing this requires also completing image v1
(proper find). Yeah, lots of tests are affected by that change.

Required-by: https://review.opendev.org/#/c/650374
Change-Id: I709d8b48cb7867fd806e2f19781bb84739363843
2019-12-17 15:05:00 +01:00

210 lines
8.4 KiB
Python

# 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 warnings
from openstack.cloud import exc
from openstack.image import _base_proxy
from openstack.image.v1 import image as _image
class Proxy(_base_proxy.BaseImageProxy):
def _create_image(self, **kwargs):
"""Create image resource from attributes
"""
return self._create(_image.Image, **kwargs)
def upload_image(self, **attrs):
"""Upload a new image from attributes
.. warning:
This method is deprecated - and also doesn't work very well.
Please stop using it immediately and switch to
`create_image`.
:param dict attrs: Keyword arguments which will be used to create
a :class:`~openstack.image.v1.image.Image`,
comprised of the properties on the Image class.
:returns: The results of image creation
:rtype: :class:`~openstack.image.v1.image.Image`
"""
warnings.warn("upload_image is deprecated. Use create_image instead.")
return self._create(_image.Image, **attrs)
def _upload_image(
self, name, filename, data, meta, wait, timeout, **image_kwargs):
# NOTE(mordred) wait and timeout parameters are unused, but
# are present for ease at calling site.
if filename and not data:
image_data = open(filename, 'rb')
else:
image_data = data
image_kwargs['properties'].update(meta)
image_kwargs['name'] = name
# TODO(mordred) Convert this to use image Resource
image = self._connection._get_and_munchify(
'image',
self.post('/images', json=image_kwargs))
checksum = image_kwargs['properties'].get(self._IMAGE_MD5_KEY, '')
try:
# Let us all take a brief moment to be grateful that this
# is not actually how OpenStack APIs work anymore
headers = {
'x-glance-registry-purge-props': 'false',
}
if checksum:
headers['x-image-meta-checksum'] = checksum
image = self._connection._get_and_munchify(
'image',
self.put(
'/images/{id}'.format(id=image.id),
headers=headers, data=image_data))
except exc.OpenStackCloudHTTPError:
self.log.debug(
"Deleting failed upload of image %s", name)
try:
self.delete('/images/{id}'.format(id=image.id))
except exc.OpenStackCloudHTTPError:
# We're just trying to clean up - if it doesn't work - shrug
self.log.warning(
"Failed deleting image after we failed uploading it.",
exc_info=True)
raise
return self._connection._normalize_image(image)
def _update_image_properties(self, image, meta, properties):
properties.update(meta)
img_props = {}
for k, v in iter(properties.items()):
if image.properties.get(k, None) != v:
img_props['x-image-meta-{key}'.format(key=k)] = v
if not img_props:
return False
self.put(
'/images/{id}'.format(id=image.id), headers=img_props)
self._connection.list_images.invalidate(self._connection)
return True
def _existing_image(self, **kwargs):
return _image.Image.existing(connection=self._connection, **kwargs)
def delete_image(self, image, ignore_missing=True):
"""Delete an image
:param image: The value can be either the ID of an image or a
:class:`~openstack.image.v1.image.Image` instance.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be
raised when the image does not exist.
When set to ``True``, no exception will be set when
attempting to delete a nonexistent image.
:returns: ``None``
"""
self._delete(_image.Image, image, ignore_missing=ignore_missing)
def find_image(self, name_or_id, ignore_missing=True):
"""Find a single image
:param name_or_id: The name or ID of a image.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be
raised when the resource does not exist.
When set to ``True``, None will be returned when
attempting to find a nonexistent resource.
:returns: One :class:`~openstack.image.v1.image.Image` or None
"""
return self._find(_image.Image, name_or_id,
ignore_missing=ignore_missing)
def get_image(self, image):
"""Get a single image
:param image: The value can be the ID of an image or a
:class:`~openstack.image.v1.image.Image` instance.
:returns: One :class:`~openstack.image.v1.image.Image`
:raises: :class:`~openstack.exceptions.ResourceNotFound`
when no resource can be found.
"""
return self._get(_image.Image, image)
def images(self, **query):
"""Return a generator of images
:param kwargs query: Optional query parameters to be sent to limit
the resources being returned.
:returns: A generator of image objects
:rtype: :class:`~openstack.image.v1.image.Image`
"""
return self._list(_image.Image, base_path='/images/detail', **query)
def update_image(self, image, **attrs):
"""Update a image
:param image: Either the ID of a image or a
:class:`~openstack.image.v1.image.Image` instance.
:attrs kwargs: The attributes to update on the image represented
by ``value``.
:returns: The updated image
:rtype: :class:`~openstack.image.v1.image.Image`
"""
return self._update(_image.Image, image, **attrs)
def download_image(self, image, stream=False, output=None,
chunk_size=1024):
"""Download an image
This will download an image to memory when ``stream=False``, or allow
streaming downloads using an iterator when ``stream=True``.
For examples of working with streamed responses, see
:ref:`download_image-stream-true`.
:param image: The value can be either the ID of an image or a
:class:`~openstack.image.v2.image.Image` instance.
:param bool stream: When ``True``, return a :class:`requests.Response`
instance allowing you to iterate over the
response data stream instead of storing its entire
contents in memory. See
:meth:`requests.Response.iter_content` for more
details. *NOTE*: If you do not consume
the entirety of the response you must explicitly
call :meth:`requests.Response.close` or otherwise
risk inefficiencies with the ``requests``
library's handling of connections.
When ``False``, return the entire
contents of the response.
:param output: Either a file object or a path to store data into.
:param int chunk_size: size in bytes to read from the wire and buffer
at one time. Defaults to 1024
:returns: When output is not given - the bytes comprising the given
Image when stream is False, otherwise a :class:`requests.Response`
instance. When output is given - a
:class:`~openstack.image.v2.image.Image` instance.
"""
image = self._get_resource(_image.Image, image)
return image.download(
self, stream=stream, output=output, chunk_size=chunk_size)