Merge "image: Add missing image import options"
This commit is contained in:
@@ -50,7 +50,14 @@ class Proxy(_base_proxy.BaseImageProxy):
|
|||||||
return self._create(_image.Image, **kwargs)
|
return self._create(_image.Image, **kwargs)
|
||||||
|
|
||||||
def import_image(
|
def import_image(
|
||||||
self, image, method='glance-direct', uri=None,
|
self,
|
||||||
|
image,
|
||||||
|
method='glance-direct',
|
||||||
|
*,
|
||||||
|
uri=None,
|
||||||
|
remote_region=None,
|
||||||
|
remote_image_id=None,
|
||||||
|
remote_service_interface=None,
|
||||||
store=None,
|
store=None,
|
||||||
stores=None,
|
stores=None,
|
||||||
all_stores=None,
|
all_stores=None,
|
||||||
@@ -67,12 +74,23 @@ class Proxy(_base_proxy.BaseImageProxy):
|
|||||||
The value can be the ID of a image or a
|
The value can be the ID of a image or a
|
||||||
:class:`~openstack.image.v2.image.Image` instance.
|
:class:`~openstack.image.v2.image.Image` instance.
|
||||||
:param method:
|
:param method:
|
||||||
Method to use for importing the image.
|
Method to use for importing the image. Not all deployments support
|
||||||
A valid value is glance-direct or web-download.
|
all methods. One of: ``glance-direct`` (default), ``web-download``,
|
||||||
|
``glance-download``, or ``copy-image``. Use of ``glance-direct``
|
||||||
|
requires the image be first staged.
|
||||||
:param uri:
|
:param uri:
|
||||||
Required only if using the web-download import method.
|
Required only if using the web-download import method.
|
||||||
This url is where the data is made available to the Image
|
This url is where the data is made available to the Image
|
||||||
service.
|
service.
|
||||||
|
:param remote_region:
|
||||||
|
The remote glance region to download the image from when using
|
||||||
|
glance-download.
|
||||||
|
:param remote_image_id:
|
||||||
|
The ID of the image to import from the remote glance when using
|
||||||
|
glance-download.
|
||||||
|
:param remote_service_interface:
|
||||||
|
The remote glance service interface to use when using
|
||||||
|
glance-download
|
||||||
:param store:
|
:param store:
|
||||||
Used when enabled_backends is activated in glance. The value
|
Used when enabled_backends is activated in glance. The value
|
||||||
can be the id of a store or a
|
can be the id of a store or a
|
||||||
@@ -95,17 +113,19 @@ class Proxy(_base_proxy.BaseImageProxy):
|
|||||||
the stores where the data has been correctly uploaded.
|
the stores where the data has been correctly uploaded.
|
||||||
Default is True.
|
Default is True.
|
||||||
|
|
||||||
:returns: None
|
:returns: The raw response from the request.
|
||||||
"""
|
"""
|
||||||
image = self._get_resource(_image.Image, image)
|
image = self._get_resource(_image.Image, image)
|
||||||
if all_stores and (store or stores):
|
if all_stores and (store or stores):
|
||||||
raise exceptions.InvalidRequest(
|
raise exceptions.InvalidRequest(
|
||||||
"all_stores is mutually exclusive with "
|
"all_stores is mutually exclusive with "
|
||||||
" store and stores")
|
"store and stores"
|
||||||
|
)
|
||||||
if store is not None:
|
if store is not None:
|
||||||
if stores:
|
if stores:
|
||||||
raise exceptions.InvalidRequest(
|
raise exceptions.InvalidRequest(
|
||||||
"store and stores are mutually exclusive")
|
"store and stores are mutually exclusive"
|
||||||
|
)
|
||||||
store = self._get_resource(_si.Store, store)
|
store = self._get_resource(_si.Store, store)
|
||||||
|
|
||||||
stores = stores or []
|
stores = stores or []
|
||||||
@@ -119,10 +139,16 @@ class Proxy(_base_proxy.BaseImageProxy):
|
|||||||
if not all([image.container_format, image.disk_format]):
|
if not all([image.container_format, image.disk_format]):
|
||||||
raise exceptions.InvalidRequest(
|
raise exceptions.InvalidRequest(
|
||||||
"Both container_format and disk_format are required for "
|
"Both container_format and disk_format are required for "
|
||||||
" importing an image")
|
"importing an image"
|
||||||
|
)
|
||||||
|
|
||||||
image.import_image(
|
return image.import_image(
|
||||||
self, method=method, uri=uri,
|
self,
|
||||||
|
method=method,
|
||||||
|
uri=uri,
|
||||||
|
remote_region=remote_region,
|
||||||
|
remote_image_id=remote_image_id,
|
||||||
|
remote_service_interface=remote_service_interface,
|
||||||
store=store,
|
store=store,
|
||||||
stores=stores,
|
stores=stores,
|
||||||
all_stores=all_stores,
|
all_stores=all_stores,
|
||||||
|
@@ -298,44 +298,66 @@ class Image(resource.Resource, tag.TagMixin, _download.DownloadMixin):
|
|||||||
self._translate_response(response, has_body=False)
|
self._translate_response(response, has_body=False)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def import_image(self, session, method='glance-direct', uri=None,
|
def import_image(
|
||||||
store=None, stores=None, all_stores=None,
|
self,
|
||||||
all_stores_must_succeed=None):
|
session,
|
||||||
|
method='glance-direct',
|
||||||
|
*,
|
||||||
|
uri=None,
|
||||||
|
remote_region=None,
|
||||||
|
remote_image_id=None,
|
||||||
|
remote_service_interface=None,
|
||||||
|
store=None,
|
||||||
|
stores=None,
|
||||||
|
all_stores=None,
|
||||||
|
all_stores_must_succeed=None,
|
||||||
|
):
|
||||||
"""Import Image via interoperable image import process"""
|
"""Import Image via interoperable image import process"""
|
||||||
if all_stores and (store or stores):
|
if all_stores and (store or stores):
|
||||||
raise exceptions.InvalidRequest(
|
raise exceptions.InvalidRequest(
|
||||||
"all_stores is mutually exclusive with"
|
'all_stores is mutually exclusive with store and stores'
|
||||||
" store and stores")
|
)
|
||||||
if store and stores:
|
if store and stores:
|
||||||
raise exceptions.InvalidRequest(
|
raise exceptions.InvalidRequest(
|
||||||
"store and stores are mutually exclusive."
|
'store and stores are mutually exclusive. stores should be '
|
||||||
" Please just use stores.")
|
'preferred.'
|
||||||
|
)
|
||||||
if store:
|
if store:
|
||||||
stores = [store]
|
stores = [store]
|
||||||
else:
|
else:
|
||||||
stores = stores or []
|
stores = stores or []
|
||||||
|
|
||||||
url = utils.urljoin(self.base_path, self.id, 'import')
|
url = utils.urljoin(self.base_path, self.id, 'import')
|
||||||
json = {'method': {'name': method}}
|
data = {'method': {'name': method}}
|
||||||
|
|
||||||
if uri:
|
if uri:
|
||||||
if method == 'web-download':
|
if method != 'web-download':
|
||||||
json['method']['uri'] = uri
|
raise exceptions.InvalidRequest(
|
||||||
else:
|
'URI is only supported with method: "web-download"'
|
||||||
raise exceptions.InvalidRequest('URI is only supported with '
|
)
|
||||||
'method: "web-download"')
|
data['method']['uri'] = uri
|
||||||
|
|
||||||
|
if remote_region and remote_image_id:
|
||||||
|
if remote_service_interface:
|
||||||
|
data['method']['glance_service_interface'] = \
|
||||||
|
remote_service_interface
|
||||||
|
data['method']['glance_region'] = remote_region
|
||||||
|
data['method']['glance_image_id'] = remote_image_id
|
||||||
|
|
||||||
if all_stores is not None:
|
if all_stores is not None:
|
||||||
json['all_stores'] = all_stores
|
data['all_stores'] = all_stores
|
||||||
if all_stores_must_succeed is not None:
|
if all_stores_must_succeed is not None:
|
||||||
json['all_stores_must_succeed'] = all_stores_must_succeed
|
data['all_stores_must_succeed'] = all_stores_must_succeed
|
||||||
for s in stores:
|
for s in stores:
|
||||||
json.setdefault('stores', [])
|
data.setdefault('stores', [])
|
||||||
json['stores'].append(s.id)
|
data['stores'].append(s.id)
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
# Backward compat
|
# Backward compat
|
||||||
if store is not None:
|
if store is not None:
|
||||||
headers = {'X-Image-Meta-Store': store.id}
|
headers = {'X-Image-Meta-Store': store.id}
|
||||||
session.post(url, json=json, headers=headers)
|
|
||||||
|
return session.post(url, json=data, headers=headers)
|
||||||
|
|
||||||
def _consume_header_attrs(self, attrs):
|
def _consume_header_attrs(self, attrs):
|
||||||
self.image_import_methods = []
|
self.image_import_methods = []
|
||||||
|
@@ -265,7 +265,7 @@ class TestImage(base.TestCase):
|
|||||||
def test_import_image(self):
|
def test_import_image(self):
|
||||||
sot = image.Image(**EXAMPLE)
|
sot = image.Image(**EXAMPLE)
|
||||||
json = {"method": {"name": "web-download", "uri": "such-a-good-uri"}}
|
json = {"method": {"name": "web-download", "uri": "such-a-good-uri"}}
|
||||||
sot.import_image(self.sess, "web-download", "such-a-good-uri")
|
sot.import_image(self.sess, "web-download", uri="such-a-good-uri")
|
||||||
self.sess.post.assert_called_with(
|
self.sess.post.assert_called_with(
|
||||||
'images/IDENTIFIER/import',
|
'images/IDENTIFIER/import',
|
||||||
headers={},
|
headers={},
|
||||||
@@ -293,7 +293,12 @@ class TestImage(base.TestCase):
|
|||||||
}
|
}
|
||||||
store = mock.MagicMock()
|
store = mock.MagicMock()
|
||||||
store.id = "ceph_1"
|
store.id = "ceph_1"
|
||||||
sot.import_image(self.sess, "web-download", "such-a-good-uri", store)
|
sot.import_image(
|
||||||
|
self.sess,
|
||||||
|
"web-download",
|
||||||
|
uri="such-a-good-uri",
|
||||||
|
store=store,
|
||||||
|
)
|
||||||
self.sess.post.assert_called_with(
|
self.sess.post.assert_called_with(
|
||||||
'images/IDENTIFIER/import',
|
'images/IDENTIFIER/import',
|
||||||
headers={'X-Image-Meta-Store': 'ceph_1'},
|
headers={'X-Image-Meta-Store': 'ceph_1'},
|
||||||
@@ -314,7 +319,7 @@ class TestImage(base.TestCase):
|
|||||||
sot.import_image(
|
sot.import_image(
|
||||||
self.sess,
|
self.sess,
|
||||||
"web-download",
|
"web-download",
|
||||||
"such-a-good-uri",
|
uri="such-a-good-uri",
|
||||||
stores=[store],
|
stores=[store],
|
||||||
)
|
)
|
||||||
self.sess.post.assert_called_with(
|
self.sess.post.assert_called_with(
|
||||||
@@ -335,7 +340,7 @@ class TestImage(base.TestCase):
|
|||||||
sot.import_image(
|
sot.import_image(
|
||||||
self.sess,
|
self.sess,
|
||||||
"web-download",
|
"web-download",
|
||||||
"such-a-good-uri",
|
uri="such-a-good-uri",
|
||||||
all_stores=True,
|
all_stores=True,
|
||||||
)
|
)
|
||||||
self.sess.post.assert_called_with(
|
self.sess.post.assert_called_with(
|
||||||
|
@@ -64,12 +64,18 @@ class TestImage(TestImageProxy):
|
|||||||
self._verify(
|
self._verify(
|
||||||
"openstack.image.v2.image.Image.import_image",
|
"openstack.image.v2.image.Image.import_image",
|
||||||
self.proxy.import_image,
|
self.proxy.import_image,
|
||||||
method_args=[original_image, "method", "uri"],
|
method_args=[original_image, "method"],
|
||||||
|
method_kwargs={
|
||||||
|
"uri": "uri",
|
||||||
|
},
|
||||||
expected_args=[self.proxy],
|
expected_args=[self.proxy],
|
||||||
expected_kwargs={
|
expected_kwargs={
|
||||||
"method": "method",
|
"method": "method",
|
||||||
"store": None,
|
"store": None,
|
||||||
"uri": "uri",
|
"uri": "uri",
|
||||||
|
"remote_region": None,
|
||||||
|
"remote_image_id": None,
|
||||||
|
"remote_service_interface": None,
|
||||||
"stores": [],
|
"stores": [],
|
||||||
"all_stores": None,
|
"all_stores": None,
|
||||||
"all_stores_must_succeed": None,
|
"all_stores_must_succeed": None,
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The ``openstack.image.Image.import_image`` method and ``import_image``
|
||||||
|
image proxy method now accept the following additional paramters:
|
||||||
|
|
||||||
|
- ``remote_region``
|
||||||
|
- ``remote_image_id``
|
||||||
|
- ``remote_service_interface``
|
||||||
|
|
||||||
|
These are required to support the ``glance-download`` image import
|
||||||
|
method.
|
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
The signatures of the ``openstack.image.v2.import_image`` has changed. All
|
||||||
|
arguments except ``image`` and ``method`` are now kwarg-only.
|
Reference in New Issue
Block a user