Tempest tests for Image_Synchronization.

Added Tempest testcases for Kingbird image_sync.
Added test_image_sync.py to resource_sync directory
which contains tempest testcases for image_sync.
Depends-on: I7fbeec5376dc3a2333a7cc9ad51108feb79a7b03
Implements: blueprint image-synchronization

Change-Id: I58b54eedd2956990991dc15a37cf0ff246341acb
This commit is contained in:
Goutham Pratapa 2017-06-30 18:50:41 +05:30
parent 3c2bc63a9f
commit 47e1cd48a2
3 changed files with 461 additions and 2 deletions

View File

@ -1,4 +1,4 @@
# Copyright 2016 Ericsson AB
# Copyright 2016 Ericsson AB.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -28,6 +28,8 @@ DEFAULT_QUOTAS = {
KEYPAIR_RESOURCE_TYPE = "keypair"
IMAGE_RESOURCE_TYPE = "image"
JOB_SUCCESS = "SUCCESS"
JOB_PROGRESS = "IN_PROGRESS"

View File

@ -13,21 +13,27 @@
# License for the specific language governing permissions and limitations
# under the License.
import ast
from glanceclient import Client as gl_client
from keystoneclient.auth.identity import v3
from keystoneclient import session
from keystoneclient.v3 import client as ks_client
from kingbirdclient.api.v1 import client as kb_client
from novaclient import client as nv_client
import six
import tempest.test
from tempest.api.compute import base as kp_base
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from kingbird.tests.tempest.scenario import consts
from novaclient import client as nv_client
CONF = config.CONF
KINGBIRD_URL = CONF.kingbird.endpoint_url + CONF.kingbird.api_version
NOVA_API_VERSION = "2.37"
GLANCE_API_VERSION = "2"
class BaseKingbirdClass(object):
@ -56,6 +62,24 @@ class BaseKingbirdClass(object):
result['target'] = target_regions
return result
def _image_sync_job_create(self, force, **kwargs):
result = dict()
images = self._create_images(**kwargs)
admin_client = self._get_admin_keystone()
regions = self._get_regions(admin_client)
target_regions = regions
target_regions.remove(self.client.region)
# Now sync the created images in other regions.
create_response = self._sync_job_create(
consts.IMAGE_RESOURCE_TYPE, target_regions, images.keys(),
force=force)
job_id = create_response.get('job_status').get('id')
result['job_id'] = job_id
result['admin'] = admin_client
result['target'] = target_regions
result['images'] = images
return result
def _check_job_status(self):
# Wait until the status of the job is not "IN_PROGRESS"
job_list_resp = self.get_sync_job_list()
@ -192,3 +216,179 @@ class BaseKBKeypairTest(kp_base.BaseV2ComputeTest):
project_id=self.client.tenant_id)
self.addCleanup(self._delete_keypair, keypair_name, **delete_params)
return body
class BaseKBImageTest(tempest.test.BaseTestCase):
"""Base test class for Image API tests."""
credentials = ['primary']
@classmethod
def skip_checks(cls):
super(BaseKBImageTest, cls).skip_checks()
if not CONF.service_available.glance:
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
@classmethod
def setup_credentials(cls):
cls.set_network_resources()
super(BaseKBImageTest, cls).setup_credentials()
@classmethod
def setup_clients(cls):
super(BaseKBImageTest, cls).setup_clients()
cls.client = cls.os.image_client_v2
@classmethod
def resource_setup(cls):
super(BaseKBImageTest, cls).resource_setup()
cls.created_images = []
@classmethod
def resource_cleanup(cls):
for image_id in cls.created_images:
test_utils.call_and_ignore_notfound_exc(
cls.client.delete_image, image_id)
for image_id in cls.created_images:
cls.client.wait_for_resource_deletion(image_id)
super(BaseKBImageTest, cls).resource_cleanup()
@classmethod
def create_and_upload_image(cls, data=None, **kwargs):
"""Wrapper that returns a test image."""
if 'name' not in kwargs:
name = data_utils.rand_name("kb-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']
cls.created_images.append(image['id'])
# Upload image to glance artifactory.
file_content = data_utils.random_bytes()
image_file = six.BytesIO(file_content)
cls.client.store_image_file(image['id'], image_file)
cls.kingbird_client = kb_client.Client(
kingbird_url=KINGBIRD_URL, auth_token=cls.client.token,
project_id=cls.client.tenant_id)
return image
@classmethod
def _get_create_params(cls, **kwargs):
return kwargs
def _get_endpoint_from_region(self, keystone_admin, region):
services_list = keystone_admin.services.list()
endpoints_list = keystone_admin.endpoints.list()
service_id = [service.id for service in
services_list if service.type == 'image'][0]
glance_endpoint = [endpoint.url for endpoint in
endpoints_list
if endpoint.service_id == service_id
and endpoint.region == region and
endpoint.interface == 'public'][0]
return glance_endpoint
def _create_images(self, **kwargs):
images = dict()
for _ in range(2):
image = self.create_and_upload_image(**kwargs)
images[image['id']] = image['name']
return images
def _sync_ami_image(self, force, ami_id):
result = dict()
admin_client = self._get_admin_keystone()
regions = self._get_regions(admin_client)
target_regions = regions
target_regions.remove(self.client.region)
# Now sync the created images in other regions.
create_response = self._sync_job_create(
consts.IMAGE_RESOURCE_TYPE, target_regions, [ami_id],
force=force)
job_id = create_response.get('job_status').get('id')
result['job_id'] = job_id
result['admin'] = admin_client
result['target'] = target_regions
result['images'] = ami_id
return result
def _check_image_properties_in_target_region(self, image, **kwargs):
if 'architecture' in kwargs:
self.assertEqual(image.architecture,
kwargs['architecture'])
if 'hypervisor_type' in kwargs:
self.assertEqual(image.hypervisor_type,
kwargs['hypervisor_type'])
if 'ramdisk_id' in kwargs:
self.assertIsNotNone(image.ramdisk_id)
if 'kernel_id' in kwargs:
self.assertIsNotNone(image.kernel_id)
self.assertEqual(image.container_format,
kwargs['container_format'])
self.assertEqual(image.disk_format,
kwargs['disk_format'])
self.assertEqual(image.visibility,
kwargs['visibility'])
def _check_images_delete_target_region(self, keystone_admin,
target_regions, images,
force, **kwargs):
for region in target_regions:
for image in images:
region_endpoint = self._get_endpoint_from_region(
keystone_admin, region)
glance_client = gl_client(GLANCE_API_VERSION,
session=self.sess,
endpoint=region_endpoint)
target_region_images = glance_client.images.list()
for target_image in target_region_images:
if target_image['name'] == images[image]:
region_image = glance_client.images.\
get(target_image['id'])
if ast.literal_eval(force):
self.assertNotEqual(region_image.id, image)
else:
self.assertEqual(region_image.id, image)
self._check_image_properties_in_target_region(
region_image, **kwargs)
glance_client.images.delete(target_image['id'])
def _check_and_delete_dependent_images_target_region(self, keystone_admin,
target_regions, image,
force, **kwargs):
for region in target_regions:
region_endpoint = self._get_endpoint_from_region(keystone_admin,
region)
glance_client = gl_client(GLANCE_API_VERSION, session=self.sess,
endpoint=region_endpoint)
target_region_images = glance_client.images.list()
for target_image in target_region_images:
if target_image['name'] == image['name']:
region_image = glance_client.images.\
get(target_image['id'])
self._check_image_properties_in_target_region(
region_image, **kwargs)
if ast.literal_eval(force):
self.assertNotEqual(region_image.id, image['id'])
else:
self.assertEqual(region_image.id, image['id'])
source_aki_image = glance_client.images.get(
region_image.kernel_id)
glance_client.images.delete(source_aki_image.id)
source_ari_image = glance_client.images.get(
region_image.ramdisk_id)
glance_client.images.delete(source_ari_image.id)
glance_client.images.delete(target_image['id'])

View File

@ -0,0 +1,257 @@
# Copyright 2017 Ericsson AB.
# 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 kingbirdclient
from tempest import config
from tempest.lib import decorators
from kingbird.tests.tempest.scenario import consts
from kingbird.tests.tempest.scenario.resource_management.sync_tests \
import base
from kingbird.tests import utils
CONF = config.CONF
FORCE = "True"
DEFAULT_FORCE = "False"
class KingbirdImageSyncTest(base.BaseKBImageTest, base.BaseKingbirdClass):
"""Here we test the basic operations of images."""
@decorators.idempotent_id('f06e43f5-b4e5-40af-92f5-1d280e398d9b')
def test_qcow2_force_image_sync(self):
"""Here we test these functionalities.
Register image, upload the image file, get image and check
if image is created in target regions.
"""
create_params = {
"container_format": CONF.image.container_formats[3],
"disk_format": CONF.image.disk_formats[6],
"visibility": 'private'
}
job_details = self._image_sync_job_create(FORCE, **create_params)
utils.wait_until_true(
lambda: self._check_job_status(),
exception=RuntimeError("Timed out waiting for job %s " %
job_details['job_id']))
# Check for resources in target_regions.
self._check_images_delete_target_region(
job_details['admin'], job_details['target'],
job_details['images'], FORCE, **create_params)
# Clean_up the database entries and resources
self.delete_db_entries(job_details['job_id'])
@decorators.idempotent_id('791c9c00-d409-4cae-a4b0-306f4dc15abb')
def test_qcow2_force_image_sync_with_extra_specs(self):
create_params = {
"container_format": CONF.image.container_formats[3],
"disk_format": CONF.image.disk_formats[6],
"visibility": 'private',
"architecture": 'arm',
"hypervisor_type": 'qemu'
}
job_details = self._image_sync_job_create(FORCE, **create_params)
utils.wait_until_true(
lambda: self._check_job_status(),
exception=RuntimeError("Timed out waiting for job %s " %
job_details['job_id']))
# Check for resources in target_regions.
self._check_images_delete_target_region(
job_details['admin'], job_details['target'],
job_details['images'], FORCE, **create_params)
# Clean_up the database entries and resources
self.delete_db_entries(job_details['job_id'])
@decorators.idempotent_id('d93d8da5-05dc-4e34-b1af-910d65aad92b')
def test_qcow2_image_sync_without_force(self):
create_params = {
"container_format": CONF.image.container_formats[3],
"disk_format": CONF.image.disk_formats[6],
"visibility": 'private',
"architecture": 'arm',
"hypervisor_type": 'qemu'
}
job_details = self._image_sync_job_create(DEFAULT_FORCE,
**create_params)
utils.wait_until_true(
lambda: self._check_job_status(),
exception=RuntimeError("Timed out waiting for job %s " %
job_details['job_id']))
# Check for resources in target_regions.
self._check_images_delete_target_region(
job_details['admin'], job_details['target'],
job_details['images'], DEFAULT_FORCE, **create_params)
# Clean_up the database entries and resources
self.delete_db_entries(job_details['job_id'])
@decorators.idempotent_id('9f51e13d-6958-46f3-a193-3352c5f157dc')
def test_get_kingbird_image_sync_list(self):
create_params = {
"container_format": CONF.image.container_formats[3],
"disk_format": CONF.image.disk_formats[6],
"visibility": 'private',
}
job_details = self._image_sync_job_create(DEFAULT_FORCE,
**create_params)
utils.wait_until_true(
lambda: self._check_job_status(),
exception=RuntimeError("Timed out waiting for job %s " %
job_details['job_id']))
# Check for resources in target_regions.
job_list_resp = self.get_sync_job_list()
self.assertEqual(job_list_resp['job_set'][0]['id'],
job_details['job_id'])
self._check_images_delete_target_region(
job_details['admin'], job_details['target'],
job_details['images'], DEFAULT_FORCE, **create_params)
# Clean_up the database entries and resources.
self.delete_db_entries(job_details['job_id'])
@decorators.idempotent_id('dfc13300-9d88-4266-bff7-3ea3e56521a7')
def test_get_image_sync_job_details(self):
create_params = {
"container_format": CONF.image.container_formats[3],
"disk_format": CONF.image.disk_formats[6],
"visibility": 'private',
}
job_details = self._image_sync_job_create(DEFAULT_FORCE,
**create_params)
utils.wait_until_true(
lambda: self._check_job_status(),
exception=RuntimeError("Timed out waiting for job %s " %
job_details['job_id']))
job_list_resp = self.get_sync_job_detail(job_details['job_id'])
for image_id in job_details['images']:
for j in range(len(job_list_resp.get('job_set'))):
if image_id in job_list_resp.get('job_set')[j].values():
self.assertEqual(
job_list_resp.get('job_set')[j].get('resource'),
image_id)
self.assertEqual(
job_list_resp.get('job_set')[0].get('resource_type'),
consts.IMAGE_RESOURCE_TYPE)
self._check_images_delete_target_region(
job_details['admin'], job_details['target'],
job_details['images'], DEFAULT_FORCE, **create_params)
# Clean_up the database entries and resources.
self.delete_db_entries(job_details['job_id'])
@decorators.idempotent_id('65bcfd9e-b9e3-42f1-a77a-3efe50e1b619')
def test_get_active_jobs_image_sync(self):
create_params = {
"container_format": CONF.image.container_formats[3],
"disk_format": CONF.image.disk_formats[6],
"visibility": 'private',
}
job_details = self._image_sync_job_create(DEFAULT_FORCE,
**create_params)
active_job = self.get_sync_job_list(consts.JOB_ACTIVE)
status = active_job.get('job_set')[0].get('sync_status')
self.assertEqual(status, consts.JOB_PROGRESS)
utils.wait_until_true(
lambda: self._check_job_status(),
exception=RuntimeError("Timed out waiting for job %s " %
job_details['job_id']))
# Check for resources in target_regions.
self._check_images_delete_target_region(
job_details['admin'], job_details['target'],
job_details['images'], DEFAULT_FORCE, **create_params)
# Clean_up the database entries
self.delete_db_entries(job_details['job_id'])
@decorators.idempotent_id('24de7f1d-afde-41eb-8eda-abf92dfee144')
def test_delete_active_jobs_image_sync(self):
create_params = {
"container_format": CONF.image.container_formats[3],
"disk_format": CONF.image.disk_formats[6],
"visibility": 'private',
}
job_details = self._image_sync_job_create(DEFAULT_FORCE,
**create_params)
self.assertRaisesRegexp(kingbirdclient.exceptions.APIException,
"406 *",
self.delete_db_entries,
job_details['job_id'])
# Actual result when we try and delete an active_job
# Clean_up the database entries
utils.wait_until_true(
lambda: self._check_job_status(),
exception=RuntimeError("Timed out waiting for job %s " %
job_details['job_id']))
# Check for resources in target_regions.
self._check_images_delete_target_region(
job_details['admin'], job_details['target'],
job_details['images'], DEFAULT_FORCE, **create_params)
# Clean_up the database entries
self.delete_db_entries(job_details['job_id'])
@decorators.idempotent_id('8f463390-0773-4e6b-a152-3a077480d4da')
def test_delete_already_deleted_job(self):
create_params = {
"container_format": CONF.image.container_formats[3],
"disk_format": CONF.image.disk_formats[6],
"visibility": 'private',
}
job_details = self._image_sync_job_create(DEFAULT_FORCE,
**create_params)
utils.wait_until_true(
lambda: self._check_job_status(),
exception=RuntimeError("Timed out waiting for job %s " %
job_details['job_id']))
# Clean_up the database entries
self.delete_db_entries(job_details['job_id'])
self.assertRaisesRegexp(kingbirdclient.exceptions.APIException,
"404 *",
self.delete_db_entries, job_details['job_id'])
# Check for resources in target_regions.
self._check_images_delete_target_region(
job_details['admin'], job_details['target'],
job_details['images'], DEFAULT_FORCE, **create_params)
def test_sync_ami_image_with_dependent_images(self):
ari_create_params = {
"container_format": CONF.image.container_formats[1],
"disk_format": CONF.image.disk_formats[1],
"visibility": 'private',
}
ari_image = self.create_and_upload_image(**ari_create_params)
aki_create_params = {
"container_format": CONF.image.container_formats[2],
"disk_format": CONF.image.disk_formats[2],
"visibility": 'private',
}
aki_image = self.create_and_upload_image(**aki_create_params)
ami_create_params = {
"container_format": CONF.image.container_formats[0],
"disk_format": CONF.image.disk_formats[0],
"visibility": 'private',
"ramdisk_id": ari_image['id'],
"kernel_id": aki_image['id']
}
ami_image = self.create_and_upload_image(**ami_create_params)
job_details = self._sync_ami_image(DEFAULT_FORCE, ami_image['id'])
# Clean_up the database entries
utils.wait_until_true(
lambda: self._check_job_status(),
exception=RuntimeError("Timed out waiting for job %s " %
job_details['job_id']))
# Check for resources in target_regions.
self._check_and_delete_dependent_images_target_region(
job_details['admin'], job_details['target'],
ami_image, DEFAULT_FORCE, **ami_create_params)
self.delete_db_entries(job_details['job_id'])