Add ability to create image from volume
OpenStack has the ability to create an image from a volume - expose it. It should be noted that literally nothing about this API is documented, although it is exposed in python-cinderclient and python-openstackclient. Change-Id: Icb06d43a63d0b120a17ce6c19807abcb3de71bcb
This commit is contained in:
parent
16a058f16e
commit
504cb05658
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added ability to create an image from a volume.
|
@ -3267,7 +3267,7 @@ class OpenStackCloud(_normalize.Normalizer):
|
||||
disk_format=None, container_format=None,
|
||||
disable_vendor_agent=True,
|
||||
wait=False, timeout=3600,
|
||||
allow_duplicates=False, meta=None, **kwargs):
|
||||
allow_duplicates=False, meta=None, volume=None, **kwargs):
|
||||
"""Upload an image to Glance.
|
||||
|
||||
:param str name: Name of the image to create. If it is a pathname
|
||||
@ -3303,6 +3303,9 @@ class OpenStackCloud(_normalize.Normalizer):
|
||||
image name. (optional, defaults to False)
|
||||
:param meta: A dict of key/value pairs to use for metadata that
|
||||
bypasses automatic type conversion.
|
||||
:param volume: Name or ID or volume object of a volume to create an
|
||||
image from. Mutually exclusive with (optional, defaults
|
||||
to None)
|
||||
|
||||
Additional kwargs will be passed to the image creation as additional
|
||||
metadata for the image and will have all values converted to string
|
||||
@ -3326,10 +3329,6 @@ class OpenStackCloud(_normalize.Normalizer):
|
||||
if not meta:
|
||||
meta = {}
|
||||
|
||||
# If there is no filename, see if name is actually the filename
|
||||
if not filename:
|
||||
name, filename = self._get_name_and_filename(name)
|
||||
|
||||
if not disk_format:
|
||||
disk_format = self.cloud_config.config['image_format']
|
||||
if not container_format:
|
||||
@ -3337,6 +3336,26 @@ class OpenStackCloud(_normalize.Normalizer):
|
||||
container_format = 'ovf'
|
||||
else:
|
||||
container_format = 'bare'
|
||||
|
||||
if volume:
|
||||
if 'id' in volume:
|
||||
volume_id = volume['id']
|
||||
else:
|
||||
volume_obj = self.get_volume(volume)
|
||||
if not volume_obj:
|
||||
raise OpenStackCloudException(
|
||||
"Volume {volume} given to create_image could"
|
||||
" not be foud".format(volume=volume))
|
||||
volume_id = volume_obj['id']
|
||||
return self._upload_image_from_volume(
|
||||
name=name, volume_id=volume_id,
|
||||
allow_duplicates=allow_duplicates,
|
||||
container_format=container_format, disk_format=disk_format,
|
||||
wait=wait, timeout=timeout)
|
||||
|
||||
# If there is no filename, see if name is actually the filename
|
||||
if not filename:
|
||||
name, filename = self._get_name_and_filename(name)
|
||||
if not (md5 or sha256):
|
||||
(md5, sha256) = self._get_file_hashes(filename)
|
||||
if allow_duplicates:
|
||||
@ -3419,6 +3438,32 @@ class OpenStackCloud(_normalize.Normalizer):
|
||||
ret.update(meta)
|
||||
return ret
|
||||
|
||||
def _upload_image_from_volume(
|
||||
self, name, volume_id, allow_duplicates,
|
||||
container_format, disk_format, wait, timeout):
|
||||
response = self._volume_client.post(
|
||||
'/volumes/{id}/action'.format(id=volume_id),
|
||||
json={
|
||||
'os-volume_upload_image': {
|
||||
'force': allow_duplicates,
|
||||
'image_name': name,
|
||||
'container_format': container_format,
|
||||
'disk_format': disk_format}})
|
||||
if not wait:
|
||||
return self.get_image(response['image_id'])
|
||||
try:
|
||||
for count in _utils._iterate_timeout(
|
||||
timeout,
|
||||
"Timeout waiting for the image to finish."):
|
||||
image_obj = self.get_image(response['image_id'])
|
||||
if image_obj and image_obj.status not in ('queued', 'saving'):
|
||||
return image_obj
|
||||
except OpenStackCloudTimeout:
|
||||
self.log.debug(
|
||||
"Timeout waiting for image to become ready. Deleting.")
|
||||
self.delete_image(response['image_id'], wait=True)
|
||||
raise
|
||||
|
||||
def _upload_image_put_v2(self, name, image_data, meta, **image_kwargs):
|
||||
|
||||
properties = image_kwargs.pop('properties', {})
|
||||
|
@ -34,7 +34,7 @@ class TestVolume(base.BaseFunctionalTestCase):
|
||||
volume_name = self.getUniqueString()
|
||||
snapshot_name = self.getUniqueString()
|
||||
self.addDetail('volume', content.text_content(volume_name))
|
||||
self.addCleanup(self.cleanup, volume_name, snapshot_name)
|
||||
self.addCleanup(self.cleanup, volume_name, snapshot_name=snapshot_name)
|
||||
volume = self.demo_cloud.create_volume(
|
||||
display_name=volume_name, size=1)
|
||||
snapshot = self.demo_cloud.create_volume_snapshot(
|
||||
@ -56,11 +56,38 @@ class TestVolume(base.BaseFunctionalTestCase):
|
||||
self.demo_cloud.delete_volume_snapshot(snapshot_name, wait=True)
|
||||
self.demo_cloud.delete_volume(volume_name, wait=True)
|
||||
|
||||
def cleanup(self, volume_name, snapshot_name):
|
||||
volume = self.demo_cloud.get_volume(volume_name)
|
||||
snapshot = self.demo_cloud.get_volume_snapshot(snapshot_name)
|
||||
def test_volume_to_image(self):
|
||||
'''Test volume export to image functionality'''
|
||||
volume_name = self.getUniqueString()
|
||||
image_name = self.getUniqueString()
|
||||
self.addDetail('volume', content.text_content(volume_name))
|
||||
self.addCleanup(self.cleanup, volume_name, image_name=image_name)
|
||||
volume = self.demo_cloud.create_volume(
|
||||
display_name=volume_name, size=1)
|
||||
image = self.demo_cloud.create_image(
|
||||
image_name, volume=volume, wait=True)
|
||||
|
||||
volume_ids = [v['id'] for v in self.demo_cloud.list_volumes()]
|
||||
self.assertIn(volume['id'], volume_ids)
|
||||
|
||||
image_list = self.demo_cloud.list_images()
|
||||
image_ids = [s['id'] for s in image_list]
|
||||
self.assertIn(image['id'], image_ids)
|
||||
|
||||
self.demo_cloud.delete_image(image_name, wait=True)
|
||||
self.demo_cloud.delete_volume(volume_name, wait=True)
|
||||
|
||||
def cleanup(self, volume_name, snapshot_name=None, image_name=None):
|
||||
# Need to delete snapshots before volumes
|
||||
if snapshot:
|
||||
self.demo_cloud.delete_volume_snapshot(snapshot_name)
|
||||
if snapshot_name:
|
||||
snapshot = self.demo_cloud.get_volume_snapshot(snapshot_name)
|
||||
if snapshot:
|
||||
self.demo_cloud.delete_volume_snapshot(
|
||||
snapshot_name, wait=True)
|
||||
if image_name:
|
||||
image = self.demo_cloud.get_image(image_name)
|
||||
if image:
|
||||
self.demo_cloud.delete_image(image_name, wait=True)
|
||||
volume = self.demo_cloud.get_volume(volume_name)
|
||||
if volume:
|
||||
self.demo_cloud.delete_volume(volume_name)
|
||||
self.demo_cloud.delete_volume(volume_name, wait=True)
|
||||
|
@ -29,6 +29,7 @@ from shade.tests.unit import base
|
||||
|
||||
NO_MD5 = '93b885adfe0da089cdf634904fd59f71'
|
||||
NO_SHA256 = '6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d'
|
||||
CINDER_URL = 'https://volume.example.com/v2/1c36b64c840a42cd9e9b931a369337f0'
|
||||
|
||||
|
||||
class BaseTestImage(base.RequestsMockTestCase):
|
||||
@ -726,3 +727,60 @@ class TestImageVersionDiscovery(BaseTestImage):
|
||||
self.cloud._image_client.endpoint_override,
|
||||
'https://image.example.com/v2/override')
|
||||
self.assert_calls()
|
||||
|
||||
|
||||
class TestImageVolume(BaseTestImage):
|
||||
|
||||
def test_create_image_volume(self):
|
||||
|
||||
volume_id = 'some-volume'
|
||||
|
||||
self.register_uri(
|
||||
'POST', '{endpoint}/volumes/{id}/action'.format(
|
||||
endpoint=CINDER_URL, id=volume_id),
|
||||
json={'os-volume_upload_image': {'image_id': self.image_id}},
|
||||
validate=dict(json={
|
||||
u'os-volume_upload_image': {
|
||||
u'container_format': u'bare',
|
||||
u'disk_format': u'qcow2',
|
||||
u'force': False,
|
||||
u'image_name': u'fake_image'}}))
|
||||
|
||||
self.use_glance()
|
||||
|
||||
self.register_uri(
|
||||
'GET', 'https://image.example.com/v2/images',
|
||||
json=self.fake_search_return)
|
||||
|
||||
self.cloud.create_image(
|
||||
'fake_image', self.imagefile.name, wait=True, timeout=1,
|
||||
volume={'id': volume_id})
|
||||
|
||||
self.assert_calls()
|
||||
|
||||
def test_create_image_volume_duplicate(self):
|
||||
|
||||
volume_id = 'some-volume'
|
||||
|
||||
self.register_uri(
|
||||
'POST', '{endpoint}/volumes/{id}/action'.format(
|
||||
endpoint=CINDER_URL, id=volume_id),
|
||||
json={'os-volume_upload_image': {'image_id': self.image_id}},
|
||||
validate=dict(json={
|
||||
u'os-volume_upload_image': {
|
||||
u'container_format': u'bare',
|
||||
u'disk_format': u'qcow2',
|
||||
u'force': True,
|
||||
u'image_name': u'fake_image'}}))
|
||||
|
||||
self.use_glance()
|
||||
|
||||
self.register_uri(
|
||||
'GET', 'https://image.example.com/v2/images',
|
||||
json=self.fake_search_return)
|
||||
|
||||
self.cloud.create_image(
|
||||
'fake_image', self.imagefile.name, wait=True, timeout=1,
|
||||
volume={'id': volume_id}, allow_duplicates=True)
|
||||
|
||||
self.assert_calls()
|
||||
|
Loading…
Reference in New Issue
Block a user