Merge "Remove Glance v1 APIs tests"
This commit is contained in:
commit
49c64a985c
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
prelude: >
|
||||
Glance v1 APIs were removed in Rocky release and last
|
||||
supported release for v1 was Queens. Tempest master does
|
||||
not support the Rocky or Queens release so we removed
|
||||
the Glance v1 tests, config option, and its service clients.
|
||||
If you would like to test the v1 APIs then you can use the old
|
||||
Tempest version.
|
|
@ -113,14 +113,11 @@ class BaseV2ComputeTest(api_version_utils.BaseMicroversionTest,
|
|||
cls.attachments_client = cls.os_primary.attachments_client_latest
|
||||
cls.snapshots_client = cls.os_primary.snapshots_client_latest
|
||||
if CONF.service_available.glance:
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
cls.images_client = cls.os_primary.image_client
|
||||
elif CONF.image_feature_enabled.api_v2:
|
||||
if CONF.image_feature_enabled.api_v2:
|
||||
cls.images_client = cls.os_primary.image_client_v2
|
||||
else:
|
||||
raise lib_exc.InvalidConfiguration(
|
||||
'Either api_v1 or api_v2 must be True in '
|
||||
'[image-feature-enabled].')
|
||||
'api_v2 must be True in [image-feature-enabled].')
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
|
|
|
@ -17,7 +17,6 @@ import io
|
|||
import random
|
||||
|
||||
from tempest.api.compute import base
|
||||
from tempest.common import image as common_image
|
||||
from tempest.common import utils
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import data_utils
|
||||
|
@ -48,23 +47,15 @@ class FlavorsV2NegativeTest(base.BaseV2ComputeTest):
|
|||
'name': data_utils.rand_name('image'),
|
||||
'container_format': CONF.image.container_formats[0],
|
||||
'disk_format': CONF.image.disk_formats[0],
|
||||
'min_ram': min_img_ram
|
||||
'min_ram': min_img_ram,
|
||||
'visibility': 'private'
|
||||
}
|
||||
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
params.update({'is_public': False})
|
||||
params = {'headers': common_image.image_meta_to_headers(**params)}
|
||||
else:
|
||||
params.update({'visibility': 'private'})
|
||||
|
||||
image = self.images_client.create_image(**params)
|
||||
image = image['image'] if 'image' in image else image
|
||||
self.addCleanup(self.images_client.delete_image, image['id'])
|
||||
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
self.images_client.update_image(image['id'], data=image_file)
|
||||
else:
|
||||
self.images_client.store_image_file(image['id'], data=image_file)
|
||||
self.images_client.store_image_file(image['id'], data=image_file)
|
||||
|
||||
self.assertEqual(min_img_ram, image['min_ram'])
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
import io
|
||||
|
||||
from tempest.api.compute import base
|
||||
from tempest.common import image as common_image
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import data_utils
|
||||
|
@ -42,17 +41,11 @@ class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
|
|||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(ImagesMetadataTestJSON, cls).setup_clients()
|
||||
# Check if glance v1 is available to determine which client to use. We
|
||||
# prefer glance v1 for the compute API tests since the compute image
|
||||
# API proxy was written for glance v1.
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
cls.glance_client = cls.os_primary.image_client
|
||||
elif CONF.image_feature_enabled.api_v2:
|
||||
if CONF.image_feature_enabled.api_v2:
|
||||
cls.glance_client = cls.os_primary.image_client_v2
|
||||
else:
|
||||
raise exceptions.InvalidConfiguration(
|
||||
'Either api_v1 or api_v2 must be True in '
|
||||
'[image-feature-enabled].')
|
||||
'api_v2 must be True in [image-feature-enabled].')
|
||||
cls.client = cls.compute_images_client
|
||||
|
||||
@classmethod
|
||||
|
@ -63,13 +56,9 @@ class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
|
|||
params = {
|
||||
'name': data_utils.rand_name('image'),
|
||||
'container_format': 'bare',
|
||||
'disk_format': 'raw'
|
||||
'disk_format': 'raw',
|
||||
'visibility': 'private'
|
||||
}
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
params.update({'is_public': False})
|
||||
params = {'headers': common_image.image_meta_to_headers(**params)}
|
||||
else:
|
||||
params.update({'visibility': 'private'})
|
||||
|
||||
body = cls.glance_client.create_image(**params)
|
||||
body = body['image'] if 'image' in body else body
|
||||
|
@ -78,10 +67,7 @@ class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
|
|||
cls.glance_client.delete_image,
|
||||
cls.image_id)
|
||||
image_file = io.BytesIO((b'*' * 1024))
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
cls.glance_client.update_image(cls.image_id, data=image_file)
|
||||
else:
|
||||
cls.glance_client.store_image_file(cls.image_id, data=image_file)
|
||||
cls.glance_client.store_image_file(cls.image_id, data=image_file)
|
||||
waiters.wait_for_image_status(cls.client, cls.image_id, 'ACTIVE')
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -19,7 +19,6 @@ import io
|
|||
import testtools
|
||||
|
||||
from tempest.api.compute import base
|
||||
from tempest.common import image as common_image
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import data_utils
|
||||
|
@ -46,17 +45,11 @@ class ListImageFiltersTestJSON(base.BaseV2ComputeTest):
|
|||
def setup_clients(cls):
|
||||
super(ListImageFiltersTestJSON, cls).setup_clients()
|
||||
cls.client = cls.compute_images_client
|
||||
# Check if glance v1 is available to determine which client to use. We
|
||||
# prefer glance v1 for the compute API tests since the compute image
|
||||
# API proxy was written for glance v1.
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
cls.glance_client = cls.os_primary.image_client
|
||||
elif CONF.image_feature_enabled.api_v2:
|
||||
if CONF.image_feature_enabled.api_v2:
|
||||
cls.glance_client = cls.os_primary.image_client_v2
|
||||
else:
|
||||
raise exceptions.InvalidConfiguration(
|
||||
'Either api_v1 or api_v2 must be True in '
|
||||
'[image-feature-enabled].')
|
||||
'api_v2 must be True in [image-feature-enabled].')
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
|
@ -66,14 +59,9 @@ class ListImageFiltersTestJSON(base.BaseV2ComputeTest):
|
|||
params = {
|
||||
'name': data_utils.rand_name(cls.__name__ + '-image'),
|
||||
'container_format': 'bare',
|
||||
'disk_format': 'raw'
|
||||
'disk_format': 'raw',
|
||||
'visibility': 'private'
|
||||
}
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
params.update({'is_public': False})
|
||||
params = {'headers':
|
||||
common_image.image_meta_to_headers(**params)}
|
||||
else:
|
||||
params.update({'visibility': 'private'})
|
||||
|
||||
body = cls.glance_client.create_image(**params)
|
||||
body = body['image'] if 'image' in body else body
|
||||
|
@ -86,10 +74,7 @@ class ListImageFiltersTestJSON(base.BaseV2ComputeTest):
|
|||
# between created_at and updated_at.
|
||||
time.sleep(1)
|
||||
image_file = io.BytesIO((b'*' * 1024))
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
cls.glance_client.update_image(image_id, data=image_file)
|
||||
else:
|
||||
cls.glance_client.store_image_file(image_id, data=image_file)
|
||||
cls.glance_client.store_image_file(image_id, data=image_file)
|
||||
waiters.wait_for_image_status(cls.client, image_id, 'ACTIVE')
|
||||
body = cls.client.show_image(image_id)['image']
|
||||
return body
|
||||
|
|
|
@ -569,17 +569,11 @@ class ServerActionsTestOtherB(ServerActionsBase):
|
|||
|
||||
# create the first and the second backup
|
||||
|
||||
# Check if glance v1 is available to determine which client to use. We
|
||||
# prefer glance v1 for the compute API tests since the compute image
|
||||
# API proxy was written for glance v1.
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
glance_client = self.os_primary.image_client
|
||||
elif CONF.image_feature_enabled.api_v2:
|
||||
if CONF.image_feature_enabled.api_v2:
|
||||
glance_client = self.os_primary.image_client_v2
|
||||
else:
|
||||
raise lib_exc.InvalidConfiguration(
|
||||
'Either api_v1 or api_v2 must be True in '
|
||||
'[image-feature-enabled].')
|
||||
'api_v2 must be True in [image-feature-enabled].')
|
||||
|
||||
backup1 = data_utils.rand_name('backup-1')
|
||||
resp = self.client.create_backup(self.server_id,
|
||||
|
@ -635,16 +629,9 @@ class ServerActionsTestOtherB(ServerActionsBase):
|
|||
'sort_key': 'created_at',
|
||||
'sort_dir': 'asc'
|
||||
}
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
for key, value in properties.items():
|
||||
params['property-%s' % key] = value
|
||||
image_list = glance_client.list_images(
|
||||
detail=True,
|
||||
**params)['images']
|
||||
else:
|
||||
# Additional properties are flattened in glance v2.
|
||||
params.update(properties)
|
||||
image_list = glance_client.list_images(params)['images']
|
||||
# Additional properties are flattened in glance v2.
|
||||
params.update(properties)
|
||||
image_list = glance_client.list_images(params)['images']
|
||||
|
||||
self.assertEqual(2, len(image_list))
|
||||
self.assertEqual((backup1, backup2),
|
||||
|
@ -668,11 +655,7 @@ class ServerActionsTestOtherB(ServerActionsBase):
|
|||
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
|
||||
glance_client.wait_for_resource_deletion(image1_id)
|
||||
oldest_backup_exist = False
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
image_list = glance_client.list_images(
|
||||
detail=True, **params)['images']
|
||||
else:
|
||||
image_list = glance_client.list_images(params)['images']
|
||||
image_list = glance_client.list_images(params)['images']
|
||||
self.assertEqual(2, len(image_list),
|
||||
'Unexpected number of images for '
|
||||
'v2:test_create_backup; was the oldest backup not '
|
||||
|
@ -733,23 +716,16 @@ class ServerActionsTestOtherB(ServerActionsBase):
|
|||
"""Test shelving and unshelving server"""
|
||||
if CONF.image_feature_enabled.api_v2:
|
||||
glance_client = self.os_primary.image_client_v2
|
||||
elif CONF.image_feature_enabled.api_v1:
|
||||
glance_client = self.os_primary.image_client
|
||||
else:
|
||||
raise lib_exc.InvalidConfiguration(
|
||||
'Either api_v1 or api_v2 must be True in '
|
||||
'[image-feature-enabled].')
|
||||
'api_v2 must be True in [image-feature-enabled].')
|
||||
compute.shelve_server(self.client, self.server_id,
|
||||
force_shelve_offload=True)
|
||||
|
||||
server = self.client.show_server(self.server_id)['server']
|
||||
image_name = server['name'] + '-shelved'
|
||||
params = {'name': image_name}
|
||||
if CONF.image_feature_enabled.api_v2:
|
||||
images = glance_client.list_images(params)['images']
|
||||
elif CONF.image_feature_enabled.api_v1:
|
||||
images = glance_client.list_images(
|
||||
detail=True, **params)['images']
|
||||
images = glance_client.list_images(params)['images']
|
||||
self.assertEqual(1, len(images))
|
||||
self.assertEqual(image_name, images[0]['name'])
|
||||
|
||||
|
|
|
@ -508,10 +508,7 @@ class ServersNegativeTestJSON(base.BaseV2ComputeTest):
|
|||
|
||||
server = self.client.show_server(self.server_id)['server']
|
||||
image_name = server['name'] + '-shelved'
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
kwargs = {'name': image_name}
|
||||
else:
|
||||
kwargs = {'params': {'name': image_name}}
|
||||
kwargs = {'params': {'name': image_name}}
|
||||
images = self.images_client.list_images(**kwargs)['images']
|
||||
self.assertEqual(1, len(images))
|
||||
self.assertEqual(image_name, images[0]['name'])
|
||||
|
|
|
@ -12,10 +12,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import io
|
||||
import time
|
||||
|
||||
from tempest.common import image as common_image
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib.common.utils import test_utils
|
||||
|
@ -56,17 +54,7 @@ class BaseImageTest(tempest.test.BaseTestCase):
|
|||
name = data_utils.rand_name(cls.__name__ + "-image")
|
||||
kwargs['name'] = name
|
||||
|
||||
params = cls._get_create_params(**kwargs)
|
||||
if data:
|
||||
# NOTE: On glance v1 API, the data should be passed on
|
||||
# a header. Then here handles the data separately.
|
||||
params['data'] = data
|
||||
|
||||
image = cls.client.create_image(**params)
|
||||
# Image objects returned by the v1 client have the image
|
||||
# data inside a dict that is keyed against 'image'.
|
||||
if 'image' in image:
|
||||
image = image['image']
|
||||
image = cls.client.create_image(**kwargs)
|
||||
cls.created_images.append(image['id'])
|
||||
cls.addClassResourceCleanup(cls.client.wait_for_resource_deletion,
|
||||
image['id'])
|
||||
|
@ -74,54 +62,6 @@ class BaseImageTest(tempest.test.BaseTestCase):
|
|||
cls.client.delete_image, image['id'])
|
||||
return image
|
||||
|
||||
@classmethod
|
||||
def _get_create_params(cls, **kwargs):
|
||||
return kwargs
|
||||
|
||||
|
||||
class BaseV1ImageTest(BaseImageTest):
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(BaseV1ImageTest, cls).skip_checks()
|
||||
if not CONF.image_feature_enabled.api_v1:
|
||||
msg = "Glance API v1 not supported"
|
||||
raise cls.skipException(msg)
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(BaseV1ImageTest, cls).setup_clients()
|
||||
cls.client = cls.os_primary.image_client
|
||||
|
||||
@classmethod
|
||||
def _get_create_params(cls, **kwargs):
|
||||
return {'headers': common_image.image_meta_to_headers(**kwargs)}
|
||||
|
||||
|
||||
class BaseV1ImageMembersTest(BaseV1ImageTest):
|
||||
|
||||
credentials = ['primary', 'alt']
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(BaseV1ImageMembersTest, cls).setup_clients()
|
||||
cls.image_member_client = cls.os_primary.image_member_client
|
||||
cls.alt_image_member_client = cls.os_alt.image_member_client
|
||||
cls.alt_img_cli = cls.os_alt.image_client
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(BaseV1ImageMembersTest, cls).resource_setup()
|
||||
cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
|
||||
|
||||
def _create_image(self):
|
||||
image_file = io.BytesIO(data_utils.random_bytes())
|
||||
image = self.create_image(container_format='bare',
|
||||
disk_format='raw',
|
||||
is_public=False,
|
||||
data=image_file)
|
||||
return image['id']
|
||||
|
||||
|
||||
class BaseV2ImageTest(BaseImageTest):
|
||||
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
# Copyright 2013 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
from tempest.api.image import base
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
|
||||
|
||||
class ImageMembersTest(base.BaseV1ImageMembersTest):
|
||||
"""Test image members"""
|
||||
|
||||
@decorators.idempotent_id('1d6ef640-3a20-4c84-8710-d95828fdb6ad')
|
||||
def test_add_image_member(self):
|
||||
"""Test adding member for image"""
|
||||
image = self._create_image()
|
||||
self.image_member_client.create_image_member(image, self.alt_tenant_id)
|
||||
body = self.image_member_client.list_image_members(image)
|
||||
members = body['members']
|
||||
members = [member['member_id'] for member in members]
|
||||
self.assertIn(self.alt_tenant_id, members)
|
||||
# get image as alt user
|
||||
self.alt_img_cli.show_image(image)
|
||||
|
||||
@decorators.idempotent_id('6a5328a5-80e8-4b82-bd32-6c061f128da9')
|
||||
def test_get_shared_images(self):
|
||||
"""Test getting shared images"""
|
||||
image = self._create_image()
|
||||
self.image_member_client.create_image_member(image, self.alt_tenant_id)
|
||||
share_image = self._create_image()
|
||||
self.image_member_client.create_image_member(share_image,
|
||||
self.alt_tenant_id)
|
||||
body = self.image_member_client.list_shared_images(
|
||||
self.alt_tenant_id)
|
||||
images = body['shared_images']
|
||||
images = [img['image_id'] for img in images]
|
||||
self.assertIn(share_image, images)
|
||||
self.assertIn(image, images)
|
||||
|
||||
@decorators.idempotent_id('a76a3191-8948-4b44-a9d6-4053e5f2b138')
|
||||
def test_remove_member(self):
|
||||
"""Test removing member from image"""
|
||||
image_id = self._create_image()
|
||||
self.image_member_client.create_image_member(image_id,
|
||||
self.alt_tenant_id)
|
||||
self.image_member_client.delete_image_member(image_id,
|
||||
self.alt_tenant_id)
|
||||
body = self.image_member_client.list_image_members(image_id)
|
||||
members = body['members']
|
||||
self.assertEmpty(members)
|
||||
self.assertRaises(
|
||||
lib_exc.NotFound, self.alt_img_cli.show_image, image_id)
|
|
@ -1,62 +0,0 @@
|
|||
# Copyright 2013 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from tempest.api.image import base
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
|
||||
|
||||
class ImageMembersNegativeTest(base.BaseV1ImageMembersTest):
|
||||
"""Negative tests of image members"""
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('147a9536-18e3-45da-91ea-b037a028f364')
|
||||
def test_add_member_with_non_existing_image(self):
|
||||
"""Add member with non existing image"""
|
||||
non_exist_image = data_utils.rand_uuid()
|
||||
self.assertRaises(lib_exc.NotFound,
|
||||
self.image_member_client.create_image_member,
|
||||
non_exist_image, self.alt_tenant_id)
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('e1559f05-b667-4f1b-a7af-518b52dc0c0f')
|
||||
def test_delete_member_with_non_existing_image(self):
|
||||
"""Delete member with non existing image"""
|
||||
non_exist_image = data_utils.rand_uuid()
|
||||
self.assertRaises(lib_exc.NotFound,
|
||||
self.image_member_client.delete_image_member,
|
||||
non_exist_image, self.alt_tenant_id)
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('f5720333-dd69-4194-bb76-d2f048addd56')
|
||||
def test_delete_member_with_non_existing_tenant(self):
|
||||
"""Delete member from image with non existing tenant"""
|
||||
image_id = self._create_image()
|
||||
non_exist_tenant = data_utils.rand_uuid_hex()
|
||||
self.assertRaises(lib_exc.NotFound,
|
||||
self.image_member_client.delete_image_member,
|
||||
image_id, non_exist_tenant)
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('f25f89e4-0b6c-453b-a853-1f80b9d7ef26')
|
||||
def test_get_image_without_membership(self):
|
||||
"""Get image without membership
|
||||
|
||||
Image is hidden from another tenants.
|
||||
"""
|
||||
image_id = self._create_image()
|
||||
self.assertRaises(lib_exc.NotFound,
|
||||
self.alt_img_cli.show_image,
|
||||
image_id)
|
|
@ -1,341 +0,0 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 io
|
||||
|
||||
from tempest.api.image import base
|
||||
from tempest.common import image as common_image
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
def get_container_and_disk_format():
|
||||
a_formats = ['ami', 'ari', 'aki']
|
||||
|
||||
container_format = CONF.image.container_formats[0]
|
||||
|
||||
# In v1, If container_format is one of ['ami', 'ari', 'aki'], then
|
||||
# disk_format must be same with container_format.
|
||||
# If they are of different item sequence in tempest.conf, such as:
|
||||
# container_formats = ami,ari,aki,bare
|
||||
# disk_formats = ari,ami,aki,vhd
|
||||
# we can select one in disk_format list that is same with container_format.
|
||||
if container_format in a_formats:
|
||||
if container_format in CONF.image.disk_formats:
|
||||
disk_format = container_format
|
||||
else:
|
||||
msg = ("The container format and the disk format don't match. "
|
||||
"Container format: %(container)s, Disk format: %(disk)s." %
|
||||
{'container': container_format, 'disk':
|
||||
CONF.image.disk_formats})
|
||||
raise exceptions.InvalidConfiguration(msg)
|
||||
else:
|
||||
disk_format = CONF.image.disk_formats[0]
|
||||
|
||||
return container_format, disk_format
|
||||
|
||||
|
||||
class CreateRegisterImagesTest(base.BaseV1ImageTest):
|
||||
"""Here we test the registration and creation of images."""
|
||||
|
||||
@decorators.idempotent_id('3027f8e6-3492-4a11-8575-c3293017af4d')
|
||||
def test_register_then_upload(self):
|
||||
"""Register, then upload an image"""
|
||||
properties = {'prop1': 'val1'}
|
||||
container_format, disk_format = get_container_and_disk_format()
|
||||
image = self.create_image(name='New Name',
|
||||
container_format=container_format,
|
||||
disk_format=disk_format,
|
||||
is_public=False,
|
||||
properties=properties)
|
||||
self.assertEqual('New Name', image.get('name'))
|
||||
self.assertFalse(image.get('is_public'))
|
||||
self.assertEqual('queued', image.get('status'))
|
||||
for key, val in properties.items():
|
||||
self.assertEqual(val, image.get('properties')[key])
|
||||
|
||||
# Now try uploading an image file
|
||||
image_file = io.BytesIO(data_utils.random_bytes())
|
||||
body = self.client.update_image(image['id'], data=image_file)['image']
|
||||
self.assertIn('size', body)
|
||||
self.assertEqual(1024, body.get('size'))
|
||||
|
||||
@decorators.idempotent_id('69da74d9-68a9-404b-9664-ff7164ccb0f5')
|
||||
def test_register_remote_image(self):
|
||||
"""Register a new remote image"""
|
||||
container_format, disk_format = get_container_and_disk_format()
|
||||
body = self.create_image(name='New Remote Image',
|
||||
container_format=container_format,
|
||||
disk_format=disk_format, is_public=False,
|
||||
location=CONF.image.http_image,
|
||||
properties={'key1': 'value1',
|
||||
'key2': 'value2'})
|
||||
self.assertEqual('New Remote Image', body.get('name'))
|
||||
self.assertFalse(body.get('is_public'))
|
||||
self.assertEqual('active', body.get('status'))
|
||||
properties = body.get('properties')
|
||||
self.assertEqual(properties['key1'], 'value1')
|
||||
self.assertEqual(properties['key2'], 'value2')
|
||||
|
||||
@decorators.idempotent_id('6d0e13a7-515b-460c-b91f-9f4793f09816')
|
||||
def test_register_http_image(self):
|
||||
"""Register a new image from an http image path url"""
|
||||
container_format, disk_format = get_container_and_disk_format()
|
||||
image = self.create_image(name='New Http Image',
|
||||
container_format=container_format,
|
||||
disk_format=disk_format, is_public=False,
|
||||
copy_from=CONF.image.http_image)
|
||||
self.assertEqual('New Http Image', image.get('name'))
|
||||
self.assertFalse(image.get('is_public'))
|
||||
waiters.wait_for_image_status(self.client, image['id'], 'active')
|
||||
self.client.show_image(image['id'])
|
||||
|
||||
@decorators.idempotent_id('05b19d55-140c-40d0-b36b-fafd774d421b')
|
||||
def test_register_image_with_min_ram(self):
|
||||
"""Register an image with min ram"""
|
||||
container_format, disk_format = get_container_and_disk_format()
|
||||
properties = {'prop1': 'val1'}
|
||||
body = self.create_image(name='New_image_with_min_ram',
|
||||
container_format=container_format,
|
||||
disk_format=disk_format,
|
||||
is_public=False,
|
||||
min_ram=40,
|
||||
properties=properties)
|
||||
self.assertEqual('New_image_with_min_ram', body.get('name'))
|
||||
self.assertFalse(body.get('is_public'))
|
||||
self.assertEqual('queued', body.get('status'))
|
||||
self.assertEqual(40, body.get('min_ram'))
|
||||
for key, val in properties.items():
|
||||
self.assertEqual(val, body.get('properties')[key])
|
||||
self.client.delete_image(body['id'])
|
||||
|
||||
|
||||
class ListImagesTest(base.BaseV1ImageTest):
|
||||
"""Here we test the listing of image information"""
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(ListImagesTest, cls).skip_checks()
|
||||
if (len(CONF.image.container_formats) < 2 or
|
||||
len(CONF.image.disk_formats) < 2):
|
||||
skip_msg = ("%s skipped as multiple container formats "
|
||||
"or disk formats are not available." % cls.__name__)
|
||||
raise cls.skipException(skip_msg)
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(ListImagesTest, cls).resource_setup()
|
||||
# We add a few images here to test the listing functionality of
|
||||
# the images API
|
||||
a_formats = ['ami', 'ari', 'aki']
|
||||
|
||||
(cls.container_format,
|
||||
container_format_alt) = CONF.image.container_formats[:2]
|
||||
cls.disk_format, cls.disk_format_alt = CONF.image.disk_formats[:2]
|
||||
if cls.container_format in a_formats:
|
||||
cls.disk_format = cls.container_format
|
||||
if container_format_alt in a_formats:
|
||||
cls.disk_format_alt = container_format_alt
|
||||
|
||||
img1 = cls._create_remote_image('one', cls.container_format,
|
||||
cls.disk_format)
|
||||
img2 = cls._create_remote_image('two', container_format_alt,
|
||||
cls.disk_format_alt)
|
||||
img3 = cls._create_remote_image('dup', cls.container_format,
|
||||
cls.disk_format)
|
||||
img4 = cls._create_remote_image('dup', cls.container_format,
|
||||
cls.disk_format)
|
||||
img5 = cls._create_standard_image('1', container_format_alt,
|
||||
cls.disk_format_alt, 42)
|
||||
img6 = cls._create_standard_image('2', container_format_alt,
|
||||
cls.disk_format_alt, 142)
|
||||
img7 = cls._create_standard_image('33', cls.container_format,
|
||||
cls.disk_format, 142)
|
||||
img8 = cls._create_standard_image('33', cls.container_format,
|
||||
cls.disk_format, 142)
|
||||
cls.created_set = set(cls.created_images)
|
||||
# same container format
|
||||
cls.same_container_format_set = set((img1, img3, img4, img7, img8))
|
||||
# same disk format
|
||||
cls.same_disk_format_set = set((img2, img5, img6))
|
||||
|
||||
# 1x with size 42
|
||||
cls.size42_set = set((img5,))
|
||||
# 3x with size 142
|
||||
cls.size142_set = set((img6, img7, img8,))
|
||||
# dup named
|
||||
cls.dup_set = set((img3, img4))
|
||||
|
||||
@classmethod
|
||||
def _create_remote_image(cls, name, container_format, disk_format):
|
||||
"""Create a new remote image and return newly-registered image-id"""
|
||||
|
||||
name = 'New Remote Image %s' % name
|
||||
location = CONF.image.http_image
|
||||
image = cls.create_image(name=name,
|
||||
container_format=container_format,
|
||||
disk_format=disk_format,
|
||||
is_public=False,
|
||||
location=location)
|
||||
return image['id']
|
||||
|
||||
@classmethod
|
||||
def _create_standard_image(cls, name, container_format,
|
||||
disk_format, size):
|
||||
"""Create a new standard image and return newly-registered image-id
|
||||
|
||||
Note that the size of the new image is a random number between
|
||||
1024 and 4096
|
||||
"""
|
||||
image_file = io.BytesIO(data_utils.random_bytes(size))
|
||||
name = 'New Standard Image %s' % name
|
||||
image = cls.create_image(name=name,
|
||||
container_format=container_format,
|
||||
disk_format=disk_format,
|
||||
is_public=False, data=image_file)
|
||||
return image['id']
|
||||
|
||||
@decorators.idempotent_id('246178ab-3b33-4212-9a4b-a7fe8261794d')
|
||||
def test_index_no_params(self):
|
||||
"""Simple test to see all fixture images returned"""
|
||||
images_list = self.client.list_images()['images']
|
||||
image_list = [image['id'] for image in images_list]
|
||||
for image_id in self.created_images:
|
||||
self.assertIn(image_id, image_list)
|
||||
|
||||
@decorators.idempotent_id('f1755589-63d6-4468-b098-589820eb4031')
|
||||
def test_index_disk_format(self):
|
||||
"""Test listing images by disk format"""
|
||||
images_list = self.client.list_images(
|
||||
disk_format=self.disk_format_alt)['images']
|
||||
for image in images_list:
|
||||
self.assertEqual(image['disk_format'], self.disk_format_alt)
|
||||
result_set = set(map(lambda x: x['id'], images_list))
|
||||
self.assertTrue(self.same_disk_format_set <= result_set)
|
||||
self.assertFalse(self.created_set - self.same_disk_format_set <=
|
||||
result_set)
|
||||
|
||||
@decorators.idempotent_id('2143655d-96d9-4bec-9188-8674206b4b3b')
|
||||
def test_index_container_format(self):
|
||||
"""Test listing images by container format"""
|
||||
images_list = self.client.list_images(
|
||||
container_format=self.container_format)['images']
|
||||
for image in images_list:
|
||||
self.assertEqual(image['container_format'], self.container_format)
|
||||
result_set = set(map(lambda x: x['id'], images_list))
|
||||
self.assertTrue(self.same_container_format_set <= result_set)
|
||||
self.assertFalse(self.created_set - self.same_container_format_set <=
|
||||
result_set)
|
||||
|
||||
@decorators.idempotent_id('feb32ac6-22bb-4a16-afd8-9454bb714b14')
|
||||
def test_index_max_size(self):
|
||||
"""Test listing images by max size"""
|
||||
images_list = self.client.list_images(size_max=42)['images']
|
||||
for image in images_list:
|
||||
self.assertLessEqual(image['size'], 42)
|
||||
result_set = set(map(lambda x: x['id'], images_list))
|
||||
self.assertTrue(self.size42_set <= result_set)
|
||||
self.assertFalse(self.created_set - self.size42_set <= result_set)
|
||||
|
||||
@decorators.idempotent_id('6ffc16d0-4cbf-4401-95c8-4ac63eac34d8')
|
||||
def test_index_min_size(self):
|
||||
"""Test listing images by min size"""
|
||||
images_list = self.client.list_images(size_min=142)['images']
|
||||
for image in images_list:
|
||||
self.assertGreaterEqual(image['size'], 142)
|
||||
result_set = set(map(lambda x: x['id'], images_list))
|
||||
self.assertTrue(self.size142_set <= result_set)
|
||||
self.assertFalse(self.size42_set <= result_set)
|
||||
|
||||
@decorators.idempotent_id('e5dc26d9-9aa2-48dd-bda5-748e1445da98')
|
||||
def test_index_status_active_detail(self):
|
||||
"""Test listing active images sorting by size in descending order"""
|
||||
images_list = self.client.list_images(detail=True,
|
||||
status='active',
|
||||
sort_key='size',
|
||||
sort_dir='desc')['images']
|
||||
top_size = images_list[0]['size'] # We have non-zero sized images
|
||||
for image in images_list:
|
||||
size = image['size']
|
||||
self.assertLessEqual(size, top_size)
|
||||
top_size = size
|
||||
self.assertEqual(image['status'], 'active')
|
||||
|
||||
@decorators.idempotent_id('097af10a-bae8-4342-bff4-edf89969ed2a')
|
||||
def test_index_name(self):
|
||||
"""Test listing images by its name"""
|
||||
images_list = self.client.list_images(
|
||||
detail=True,
|
||||
name='New Remote Image dup')['images']
|
||||
result_set = set(map(lambda x: x['id'], images_list))
|
||||
for image in images_list:
|
||||
self.assertEqual(image['name'], 'New Remote Image dup')
|
||||
self.assertTrue(self.dup_set <= result_set)
|
||||
self.assertFalse(self.created_set - self.dup_set <= result_set)
|
||||
|
||||
|
||||
class UpdateImageMetaTest(base.BaseV1ImageTest):
|
||||
"""Test image metadata"""
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(UpdateImageMetaTest, cls).resource_setup()
|
||||
container_format, disk_format = get_container_and_disk_format()
|
||||
cls.image_id = cls._create_standard_image('1', container_format,
|
||||
disk_format, 42)
|
||||
|
||||
@classmethod
|
||||
def _create_standard_image(cls, name, container_format,
|
||||
disk_format, size):
|
||||
"""Create a new standard image and return newly-registered image-id"""
|
||||
|
||||
image_file = io.BytesIO(data_utils.random_bytes(size))
|
||||
name = 'New Standard Image %s' % name
|
||||
image = cls.create_image(name=name,
|
||||
container_format=container_format,
|
||||
disk_format=disk_format,
|
||||
is_public=False, data=image_file,
|
||||
properties={'key1': 'value1'})
|
||||
return image['id']
|
||||
|
||||
@decorators.idempotent_id('01752c1c-0275-4de3-9e5b-876e44541928')
|
||||
def test_list_image_metadata(self):
|
||||
"""Test listing image metadata"""
|
||||
# All metadata key/value pairs for an image should be returned
|
||||
resp = self.client.check_image(self.image_id)
|
||||
resp_metadata = common_image.get_image_meta_from_headers(resp)
|
||||
expected = {'key1': 'value1'}
|
||||
self.assertEqual(expected, resp_metadata['properties'])
|
||||
|
||||
@decorators.idempotent_id('d6d7649c-08ce-440d-9ea7-e3dda552f33c')
|
||||
def test_update_image_metadata(self):
|
||||
"""Test updating image metadata"""
|
||||
# The metadata for the image should match the updated values
|
||||
req_metadata = {'key1': 'alt1', 'key2': 'value2'}
|
||||
resp = self.client.check_image(self.image_id)
|
||||
metadata = common_image.get_image_meta_from_headers(resp)
|
||||
self.assertEqual(metadata['properties'], {'key1': 'value1'})
|
||||
metadata['properties'].update(req_metadata)
|
||||
headers = common_image.image_meta_to_headers(
|
||||
properties=metadata['properties'])
|
||||
self.client.update_image(self.image_id, headers=headers)
|
||||
resp = self.client.check_image(self.image_id)
|
||||
resp_metadata = common_image.get_image_meta_from_headers(resp)
|
||||
self.assertEqual(req_metadata, resp_metadata['properties'])
|
|
@ -1,82 +0,0 @@
|
|||
# Copyright 2013 IBM Corp.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
from tempest.api.image import base
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
|
||||
|
||||
class CreateDeleteImagesNegativeTest(base.BaseV1ImageTest):
|
||||
"""Here are negative tests for the deletion and creation of images."""
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('036ede36-6160-4463-8c01-c781eee6369d')
|
||||
def test_register_with_invalid_container_format(self):
|
||||
"""Create image with invalid container format
|
||||
|
||||
Negative tests for invalid data supplied to POST /images
|
||||
"""
|
||||
self.assertRaises(lib_exc.BadRequest, self.client.create_image,
|
||||
headers={'x-image-meta-name': 'test',
|
||||
'x-image-meta-container_format': 'wrong',
|
||||
'x-image-meta-disk_format': 'vhd'})
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('993face5-921d-4e84-aabf-c1bba4234a67')
|
||||
def test_register_with_invalid_disk_format(self):
|
||||
"""Create image with invalid disk format"""
|
||||
self.assertRaises(lib_exc.BadRequest, self.client.create_image,
|
||||
headers={'x-image-meta-name': 'test',
|
||||
'x-image-meta-container_format': 'bare',
|
||||
'x-image-meta-disk_format': 'wrong'})
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('ec652588-7e3c-4b67-a2f2-0fa96f57c8fc')
|
||||
def test_delete_non_existent_image(self):
|
||||
"""Return an error while trying to delete a non-existent image"""
|
||||
|
||||
non_existent_image_id = data_utils.rand_uuid()
|
||||
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
|
||||
non_existent_image_id)
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('04f72aa3-fcec-45a3-81a3-308ef7cc82bc')
|
||||
def test_delete_image_blank_id(self):
|
||||
"""Return an error while trying to delete an image with blank Id"""
|
||||
self.assertRaises(lib_exc.NotFound, self.client.delete_image, '')
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('950e5054-a3c7-4dee-ada5-e576f1087abd')
|
||||
def test_delete_image_non_hex_string_id(self):
|
||||
"""Return an error while trying to delete an image with non hex id"""
|
||||
invalid_image_id = data_utils.rand_uuid()[:-1] + "j"
|
||||
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
|
||||
invalid_image_id)
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('4ed757cd-450c-44b1-9fd1-c819748c650d')
|
||||
def test_delete_image_negative_image_id(self):
|
||||
"""Return an error while trying to delete an image with negative id"""
|
||||
self.assertRaises(lib_exc.NotFound, self.client.delete_image, -1)
|
||||
|
||||
@decorators.attr(type=['negative'])
|
||||
@decorators.idempotent_id('a4a448ab-3db2-4d2d-b9b2-6a1271241dfe')
|
||||
def test_delete_image_id_over_character_limit(self):
|
||||
"""Return an error while trying to delete image with id over limit"""
|
||||
overlimit_image_id = data_utils.rand_uuid() + "1"
|
||||
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
|
||||
overlimit_image_id)
|
|
@ -83,8 +83,6 @@ class Manager(clients.ServiceClients):
|
|||
|
||||
def _set_image_clients(self):
|
||||
if CONF.service_available.glance:
|
||||
self.image_client = self.image_v1.ImagesClient()
|
||||
self.image_member_client = self.image_v1.ImageMembersClient()
|
||||
self.image_client_v2 = self.image_v2.ImagesClient()
|
||||
self.image_member_client_v2 = self.image_v2.ImageMembersClient()
|
||||
self.image_cache_client = self.image_v2.ImageCacheClient()
|
||||
|
|
|
@ -118,25 +118,16 @@ def verify_glance_api_versions(os, update):
|
|||
# Since we want to verify that the configuration is correct, we cannot
|
||||
# rely on a specific version of the API being available.
|
||||
try:
|
||||
_, versions = os.image_v1.ImagesClient().get_versions()
|
||||
versions = os.image_v2.VersionsClient().list_versions()['versions']
|
||||
versions = [x['id'] for x in versions]
|
||||
except lib_exc.NotFound:
|
||||
# If not found, we use v2. The assumption is that either v1 or v2
|
||||
# are available, since glance is marked as available in the catalog.
|
||||
# If not, glance should be disabled in Tempest conf.
|
||||
try:
|
||||
versions = os.image_v2.VersionsClient().list_versions()['versions']
|
||||
versions = [x['id'] for x in versions]
|
||||
except lib_exc.NotFound:
|
||||
msg = ('Glance is available in the catalog, but no known version, '
|
||||
'(v1.x or v2.x) of Glance could be found, so Glance should '
|
||||
'be configured as not available')
|
||||
LOG.warning(msg)
|
||||
print_and_or_update('glance', 'service-available', False, update)
|
||||
return
|
||||
msg = ('Glance is available in the catalog, but no known version, '
|
||||
'of Glance could be found, so Glance should '
|
||||
'be configured as not available')
|
||||
LOG.warning(msg)
|
||||
print_and_or_update('glance', 'service-available', False, update)
|
||||
return
|
||||
|
||||
if CONF.image_feature_enabled.api_v1 != contains_version('v1.', versions):
|
||||
print_and_or_update('api_v1', 'image-feature-enabled',
|
||||
not CONF.image_feature_enabled.api_v1, update)
|
||||
if CONF.image_feature_enabled.api_v2 != contains_version('v2.', versions):
|
||||
print_and_or_update('api_v2', 'image-feature-enabled',
|
||||
not CONF.image_feature_enabled.api_v2, update)
|
||||
|
|
|
@ -16,12 +16,10 @@ import time
|
|||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from tempest.common import image as common_image
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.lib.common.utils import test_utils
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
from tempest.lib.services.image.v1 import images_client as images_v1_client
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -156,17 +154,7 @@ def wait_for_image_status(client, image_id, status):
|
|||
The client should have a show_image(image_id) method to get the image.
|
||||
The client should also have build_interval and build_timeout attributes.
|
||||
"""
|
||||
if isinstance(client, images_v1_client.ImagesClient):
|
||||
# The 'check_image' method is used here because the show_image method
|
||||
# returns image details plus the image itself which is very expensive.
|
||||
# The 'check_image' method returns just image details.
|
||||
def _show_image_v1(image_id):
|
||||
resp = client.check_image(image_id)
|
||||
return common_image.get_image_meta_from_headers(resp)
|
||||
|
||||
show_image = _show_image_v1
|
||||
else:
|
||||
show_image = client.show_image
|
||||
show_image = client.show_image
|
||||
|
||||
current_status = 'An unknown status'
|
||||
start = int(time.time())
|
||||
|
|
|
@ -719,14 +719,6 @@ ImageFeaturesGroup = [
|
|||
'are current one. In future, Tempest will '
|
||||
'test v2 APIs only so this config option '
|
||||
'will be removed.'),
|
||||
cfg.BoolOpt('api_v1',
|
||||
default=False,
|
||||
help="Is the v1 image API enabled",
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason='Glance v1 APIs are deprecated and v2 APIs '
|
||||
'are current one. In future, Tempest will '
|
||||
'test v2 APIs only so this config option '
|
||||
'will be removed.'),
|
||||
# Image import feature is setup in devstack victoria onwards.
|
||||
# Once all stable branches setup the same via glance standalone
|
||||
# mode or with uwsgi, we can remove this config option.
|
||||
|
|
|
@ -48,7 +48,6 @@ def tempest_modules():
|
|||
'placement': placement,
|
||||
'identity.v2': identity.v2,
|
||||
'identity.v3': identity.v3,
|
||||
'image.v1': image.v1,
|
||||
'image.v2': image.v2,
|
||||
'network': network,
|
||||
'object-storage': object_storage,
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
# License for the specific language governing permissions and limitations under
|
||||
# the License.
|
||||
|
||||
from tempest.lib.services.image import v1
|
||||
from tempest.lib.services.image import v2
|
||||
|
||||
__all__ = ['v1', 'v2']
|
||||
__all__ = ['v2']
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
# Copyright (c) 2016 Hewlett-Packard Enterprise 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 warnings
|
||||
|
||||
from tempest.lib.services.image.v1.image_members_client import \
|
||||
ImageMembersClient
|
||||
from tempest.lib.services.image.v1.images_client import ImagesClient
|
||||
|
||||
__all__ = ['ImageMembersClient', 'ImagesClient']
|
||||
|
||||
|
||||
warnings.warn(
|
||||
"The tempest.lib.services.image.v1 module (Image v1 APIs service "
|
||||
"clients) is deprecated in favor of tempest.lib.services.image.v2 "
|
||||
"(Image v2 APIs service clients) and will be removed once Tempest stop "
|
||||
"supporting stable Ussuri.", DeprecationWarning)
|
|
@ -1,66 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from oslo_serialization import jsonutils as json
|
||||
|
||||
from tempest.lib.common import rest_client
|
||||
|
||||
|
||||
class ImageMembersClient(rest_client.RestClient):
|
||||
api_version = "v1"
|
||||
|
||||
def list_image_members(self, image_id):
|
||||
"""List all members of an image."""
|
||||
url = 'images/%s/members' % image_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def list_shared_images(self, tenant_id):
|
||||
"""List image memberships for the given tenant.
|
||||
|
||||
For a full list of available parameters, please refer to the official
|
||||
API reference:
|
||||
https://docs.openstack.org/api-ref/image/v1/#list-shared-images
|
||||
"""
|
||||
|
||||
url = 'shared-images/%s' % tenant_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def create_image_member(self, image_id, member_id, **kwargs):
|
||||
"""Add a member to an image.
|
||||
|
||||
For a full list of available parameters, please refer to the official
|
||||
API reference:
|
||||
https://docs.openstack.org/api-ref/image/v1/#add-member-to-image
|
||||
"""
|
||||
url = 'images/%s/members/%s' % (image_id, member_id)
|
||||
body = json.dumps({'member': kwargs})
|
||||
resp, __ = self.put(url, body)
|
||||
self.expected_success(204, resp.status)
|
||||
return rest_client.ResponseBody(resp)
|
||||
|
||||
def delete_image_member(self, image_id, member_id):
|
||||
"""Removes a membership from the image.
|
||||
|
||||
For a full list of available parameters, please refer to the official
|
||||
API reference:
|
||||
https://docs.openstack.org/api-ref/image/v1/#remove-member
|
||||
"""
|
||||
url = 'images/%s/members/%s' % (image_id, member_id)
|
||||
resp, __ = self.delete(url)
|
||||
self.expected_success(204, resp.status)
|
||||
return rest_client.ResponseBody(resp)
|
|
@ -1,155 +0,0 @@
|
|||
# Copyright 2013 IBM Corp.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 functools
|
||||
from urllib import parse as urllib
|
||||
|
||||
from oslo_serialization import jsonutils as json
|
||||
|
||||
from tempest.lib.common import rest_client
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
|
||||
CHUNKSIZE = 1024 * 64 # 64kB
|
||||
|
||||
|
||||
class ImagesClient(rest_client.RestClient):
|
||||
api_version = "v1"
|
||||
|
||||
def _create_with_data(self, headers, data):
|
||||
# We are going to do chunked transfert, so split the input data
|
||||
# info fixed-sized chunks.
|
||||
headers['Content-Type'] = 'application/octet-stream'
|
||||
data = iter(functools.partial(data.read, CHUNKSIZE), b'')
|
||||
resp, body = self.request('POST', 'images',
|
||||
headers=headers, body=data, chunked=True)
|
||||
self._error_checker(resp, body)
|
||||
body = json.loads(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def _update_with_data(self, image_id, headers, data):
|
||||
# We are going to do chunked transfert, so split the input data
|
||||
# info fixed-sized chunks.
|
||||
headers['Content-Type'] = 'application/octet-stream'
|
||||
data = iter(functools.partial(data.read, CHUNKSIZE), b'')
|
||||
url = 'images/%s' % image_id
|
||||
resp, body = self.request('PUT', url, headers=headers,
|
||||
body=data, chunked=True)
|
||||
self._error_checker(resp, body)
|
||||
body = json.loads(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
@property
|
||||
def http(self):
|
||||
if self._http is None:
|
||||
self._http = self._get_http()
|
||||
return self._http
|
||||
|
||||
def create_image(self, data=None, headers=None):
|
||||
"""Create an image.
|
||||
|
||||
For a full list of available parameters, please refer to the official
|
||||
API reference:
|
||||
https://docs.openstack.org/api-ref/image/v1/index.html#create-image
|
||||
"""
|
||||
if headers is None:
|
||||
headers = {}
|
||||
|
||||
if data is not None:
|
||||
return self._create_with_data(headers, data)
|
||||
|
||||
resp, body = self.post('images', None, headers)
|
||||
self.expected_success(201, resp.status)
|
||||
body = json.loads(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def update_image(self, image_id, data=None, headers=None):
|
||||
"""Update an image.
|
||||
|
||||
For a full list of available parameters, please refer to the official
|
||||
API reference:
|
||||
https://docs.openstack.org/api-ref/image/v1/index.html#update-image
|
||||
"""
|
||||
if headers is None:
|
||||
headers = {}
|
||||
|
||||
if data is not None:
|
||||
return self._update_with_data(image_id, headers, data)
|
||||
|
||||
url = 'images/%s' % image_id
|
||||
resp, body = self.put(url, None, headers)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def delete_image(self, image_id):
|
||||
url = 'images/%s' % image_id
|
||||
resp, body = self.delete(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def list_images(self, detail=False, **kwargs):
|
||||
"""Return a list of all images filtered by input parameters.
|
||||
|
||||
For a full list of available parameters, please refer to the official
|
||||
API reference:
|
||||
https://docs.openstack.org/api-ref/image/v1/#list-images
|
||||
|
||||
Most parameters except the following are passed to the API without
|
||||
any changes.
|
||||
:param changes_since: The name is changed to changes-since
|
||||
"""
|
||||
url = 'images'
|
||||
|
||||
if detail:
|
||||
url += '/detail'
|
||||
|
||||
if 'changes_since' in kwargs:
|
||||
kwargs['changes-since'] = kwargs.pop('changes_since')
|
||||
|
||||
if kwargs:
|
||||
url += '?%s' % urllib.urlencode(kwargs)
|
||||
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def check_image(self, image_id):
|
||||
"""Check image metadata."""
|
||||
url = 'images/%s' % image_id
|
||||
resp, body = self.head(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def show_image(self, image_id):
|
||||
"""Get image details plus the image itself."""
|
||||
url = 'images/%s' % image_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return rest_client.ResponseBodyData(resp, body)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
resp = self.check_image(id)
|
||||
if resp.response["x-image-meta-status"] == 'deleted':
|
||||
return True
|
||||
except lib_exc.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'image_meta'
|
|
@ -25,7 +25,6 @@ from oslo_serialization import jsonutils as json
|
|||
from oslo_utils import netutils
|
||||
|
||||
from tempest.common import compute
|
||||
from tempest.common import image as common_image
|
||||
from tempest.common.utils.linux import remote_client
|
||||
from tempest.common.utils import net_utils
|
||||
from tempest.common import waiters
|
||||
|
@ -124,15 +123,11 @@ class ScenarioTest(tempest.test.BaseTestCase):
|
|||
"""This setup the service clients for the tests"""
|
||||
super(ScenarioTest, cls).setup_clients()
|
||||
if CONF.service_available.glance:
|
||||
# Check if glance v1 is available to determine which client to use.
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
cls.image_client = cls.os_primary.image_client
|
||||
elif CONF.image_feature_enabled.api_v2:
|
||||
if CONF.image_feature_enabled.api_v2:
|
||||
cls.image_client = cls.os_primary.image_client_v2
|
||||
else:
|
||||
raise lib_exc.InvalidConfiguration(
|
||||
'Either api_v1 or api_v2 must be True in '
|
||||
'[image-feature-enabled].')
|
||||
'api_v2 must be True in [image-feature-enabled].')
|
||||
|
||||
cls.setup_compute_client(cls)
|
||||
cls.setup_network_client(cls)
|
||||
|
@ -371,11 +366,7 @@ class ScenarioTest(tempest.test.BaseTestCase):
|
|||
if size is None:
|
||||
size = CONF.volume.volume_size
|
||||
if imageRef:
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
resp = self.image_client.check_image(imageRef)
|
||||
image = common_image.get_image_meta_from_headers(resp)
|
||||
else:
|
||||
image = self.image_client.show_image(imageRef)
|
||||
image = self.image_client.show_image(imageRef)
|
||||
min_disk = image.get('min_disk')
|
||||
size = max(size, min_disk)
|
||||
if name is None:
|
||||
|
@ -796,27 +787,18 @@ class ScenarioTest(tempest.test.BaseTestCase):
|
|||
'name': name,
|
||||
'container_format': img_container_format,
|
||||
'disk_format': img_disk_format or img_container_format,
|
||||
'visibility': 'private'
|
||||
}
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
params['is_public'] = 'False'
|
||||
if img_properties:
|
||||
params['properties'] = img_properties
|
||||
params = {'headers': common_image.image_meta_to_headers(**params)}
|
||||
else:
|
||||
params['visibility'] = 'private'
|
||||
# Additional properties are flattened out in the v2 API.
|
||||
if img_properties:
|
||||
params.update(img_properties)
|
||||
# Additional properties are flattened out in the v2 API.
|
||||
if img_properties:
|
||||
params.update(img_properties)
|
||||
params.update(kwargs)
|
||||
body = self.image_client.create_image(**params)
|
||||
image = body['image'] if 'image' in body else body
|
||||
self.addCleanup(self.image_client.delete_image, image['id'])
|
||||
self.assertEqual("queued", image['status'])
|
||||
with open(img_path, 'rb') as image_file:
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
self.image_client.update_image(image['id'], data=image_file)
|
||||
else:
|
||||
self.image_client.store_image_file(image['id'], image_file)
|
||||
self.image_client.store_image_file(image['id'], image_file)
|
||||
LOG.debug("image:%s", image['id'])
|
||||
return image['id']
|
||||
|
||||
|
@ -864,15 +846,9 @@ class ScenarioTest(tempest.test.BaseTestCase):
|
|||
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
|
||||
_image_client.delete_image, image_id)
|
||||
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
# In glance v1 the additional properties are stored in the headers
|
||||
resp = _image_client.check_image(image_id)
|
||||
snapshot_image = common_image.get_image_meta_from_headers(resp)
|
||||
image_props = snapshot_image.get('properties', {})
|
||||
else:
|
||||
# In glance v2 the additional properties are flattened.
|
||||
snapshot_image = _image_client.show_image(image_id)
|
||||
image_props = snapshot_image
|
||||
# In glance v2 the additional properties are flattened.
|
||||
snapshot_image = _image_client.show_image(image_id)
|
||||
image_props = snapshot_image
|
||||
|
||||
bdm = image_props.get('block_device_mapping')
|
||||
if bdm:
|
||||
|
|
|
@ -178,13 +178,13 @@ class TestDiscovery(base.TestCase):
|
|||
def test_verify_glance_version_no_v2_with_v1_1(self):
|
||||
# This test verifies that wrong config api_v2 = True is detected
|
||||
class FakeClient(object):
|
||||
def get_versions(self):
|
||||
return (None, ['v1.1'])
|
||||
def list_versions(self):
|
||||
return {'versions': [{'id': 'v1.1'}]}
|
||||
|
||||
fake_os = mock.MagicMock()
|
||||
fake_module = mock.MagicMock()
|
||||
fake_module.ImagesClient = FakeClient
|
||||
fake_os.image_v1 = fake_module
|
||||
fake_module.VersionsClient = FakeClient
|
||||
fake_os.image_v2 = fake_module
|
||||
with mock.patch.object(verify_tempest_config,
|
||||
'print_and_or_update') as print_mock:
|
||||
verify_tempest_config.verify_glance_api_versions(fake_os, True)
|
||||
|
@ -194,53 +194,28 @@ class TestDiscovery(base.TestCase):
|
|||
def test_verify_glance_version_no_v2_with_v1_0(self):
|
||||
# This test verifies that wrong config api_v2 = True is detected
|
||||
class FakeClient(object):
|
||||
def get_versions(self):
|
||||
return (None, ['v1.0'])
|
||||
def list_versions(self):
|
||||
return {'versions': [{'id': 'v1.0'}]}
|
||||
|
||||
fake_os = mock.MagicMock()
|
||||
fake_module = mock.MagicMock()
|
||||
fake_module.ImagesClient = FakeClient
|
||||
fake_os.image_v1 = fake_module
|
||||
fake_module.VersionsClient = FakeClient
|
||||
fake_os.image_v2 = fake_module
|
||||
with mock.patch.object(verify_tempest_config,
|
||||
'print_and_or_update') as print_mock:
|
||||
verify_tempest_config.verify_glance_api_versions(fake_os, True)
|
||||
print_mock.assert_called_with('api_v2', 'image-feature-enabled',
|
||||
False, True)
|
||||
|
||||
def test_verify_glance_version_no_v1(self):
|
||||
# This test verifies that wrong config api_v1 = True is detected
|
||||
class FakeClient(object):
|
||||
def get_versions(self):
|
||||
raise lib_exc.NotFound()
|
||||
|
||||
def list_versions(self):
|
||||
return {'versions': [{'id': 'v2.0'}]}
|
||||
|
||||
fake_os = mock.MagicMock()
|
||||
fake_module = mock.MagicMock()
|
||||
fake_module.ImagesClient = FakeClient
|
||||
fake_module.VersionsClient = FakeClient
|
||||
fake_os.image_v1 = fake_module
|
||||
fake_os.image_v2 = fake_module
|
||||
with mock.patch.object(verify_tempest_config,
|
||||
'print_and_or_update') as print_mock:
|
||||
verify_tempest_config.verify_glance_api_versions(fake_os, True)
|
||||
print_mock.assert_not_called()
|
||||
|
||||
def test_verify_glance_version_no_version(self):
|
||||
# This test verifies that wrong config api_v1 = True is detected
|
||||
# This test verifies that wrong config api_v2 = True is detected
|
||||
class FakeClient(object):
|
||||
def get_versions(self):
|
||||
raise lib_exc.NotFound()
|
||||
|
||||
def list_versions(self):
|
||||
raise lib_exc.NotFound()
|
||||
|
||||
fake_os = mock.MagicMock()
|
||||
fake_module = mock.MagicMock()
|
||||
fake_module.ImagesClient = FakeClient
|
||||
fake_module.VersionsClient = FakeClient
|
||||
fake_os.image_v1 = fake_module
|
||||
fake_os.image_v2 = fake_module
|
||||
with mock.patch.object(verify_tempest_config,
|
||||
'print_and_or_update') as print_mock:
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
# Copyright 2016 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from tempest.lib.services.image.v1 import image_members_client
|
||||
from tempest.tests.lib import fake_auth_provider
|
||||
from tempest.tests.lib.services import base
|
||||
|
||||
|
||||
class TestImageMembersClient(base.BaseServiceTest):
|
||||
FAKE_LIST_IMAGE_MEMBERS = {
|
||||
"members": [
|
||||
{
|
||||
"created_at": "2013-10-07T17:58:03Z",
|
||||
"image_id": "dbc999e3-c52f-4200-bedd-3b18fe7f87fe",
|
||||
"member_id": "123456789",
|
||||
"status": "pending",
|
||||
"updated_at": "2013-10-07T17:58:03Z"
|
||||
},
|
||||
{
|
||||
"created_at": "2013-10-07T17:58:55Z",
|
||||
"image_id": "dbc999e3-c52f-4200-bedd-3b18fe7f87fe",
|
||||
"member_id": "987654321",
|
||||
"status": "accepted",
|
||||
"updated_at": "2013-10-08T12:08:55Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestImageMembersClient, self).setUp()
|
||||
fake_auth = fake_auth_provider.FakeAuthProvider()
|
||||
self.client = image_members_client.ImageMembersClient(fake_auth,
|
||||
'image',
|
||||
'regionOne')
|
||||
|
||||
def _test_list_image_members(self, bytes_body=False):
|
||||
self.check_service_client_function(
|
||||
self.client.list_image_members,
|
||||
'tempest.lib.common.rest_client.RestClient.get',
|
||||
self.FAKE_LIST_IMAGE_MEMBERS,
|
||||
bytes_body,
|
||||
image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e")
|
||||
|
||||
def _test_create_image_member(self, bytes_body=False):
|
||||
self.check_service_client_function(
|
||||
self.client.create_image_member,
|
||||
'tempest.lib.common.rest_client.RestClient.put',
|
||||
{},
|
||||
bytes_body,
|
||||
image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
|
||||
member_id="8989447062e04a818baf9e073fd04fa7",
|
||||
status=204)
|
||||
|
||||
def test_list_image_members_with_str_body(self):
|
||||
self._test_list_image_members()
|
||||
|
||||
def test_list_image_members_with_bytes_body(self):
|
||||
self._test_list_image_members(bytes_body=True)
|
||||
|
||||
def test_create_image_member_with_str_body(self):
|
||||
self._test_create_image_member()
|
||||
|
||||
def test_create_image_member_with_bytes_body(self):
|
||||
self._test_create_image_member(bytes_body=True)
|
||||
|
||||
def test_delete_image_member(self):
|
||||
self.check_service_client_function(
|
||||
self.client.delete_image_member,
|
||||
'tempest.lib.common.rest_client.RestClient.delete',
|
||||
{},
|
||||
image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e",
|
||||
member_id="8989447062e04a818baf9e073fd04fa7",
|
||||
status=204)
|
|
@ -37,7 +37,7 @@ class RegistryFixture(fixtures.Fixture):
|
|||
def __init__(self):
|
||||
"""Initialise the registry fixture"""
|
||||
self.services = set(['compute', 'identity.v2', 'identity.v3',
|
||||
'image.v1', 'image.v2', 'network', 'placement',
|
||||
'image.v2', 'network', 'placement',
|
||||
'volume.v2', 'volume.v3', 'object-storage'])
|
||||
|
||||
def _setUp(self):
|
||||
|
|
Loading…
Reference in New Issue