Merge pull request #437 from gabriel-samfira/add-ironic-tests
Add Ironic tests
This commit is contained in:
@@ -29,6 +29,7 @@ python-ceilometerclient
|
||||
python-cinderclient
|
||||
python-glanceclient
|
||||
python-heatclient
|
||||
python-ironicclient
|
||||
python-keystoneclient
|
||||
python-manilaclient
|
||||
python-neutronclient
|
||||
|
||||
@@ -504,7 +504,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
glance_mock.images.upload.assert_called_once_with(
|
||||
'9d1125af',
|
||||
f(),
|
||||
)
|
||||
backend=None)
|
||||
self.resource_reaches_status.assert_called_once_with(
|
||||
glance_mock.images,
|
||||
'9d1125af',
|
||||
@@ -529,7 +529,11 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
self.upload_image_to_glance.assert_called_once_with(
|
||||
glance_mock,
|
||||
'wibbly/c.img',
|
||||
'bob')
|
||||
'bob',
|
||||
backend=None,
|
||||
disk_format='qcow2',
|
||||
visibility='public',
|
||||
container_format='bare')
|
||||
|
||||
def test_create_image_pass_directory(self):
|
||||
glance_mock = mock.MagicMock()
|
||||
@@ -549,7 +553,11 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
self.upload_image_to_glance.assert_called_once_with(
|
||||
glance_mock,
|
||||
'tests/c.img',
|
||||
'bob')
|
||||
'bob',
|
||||
backend=None,
|
||||
disk_format='qcow2',
|
||||
visibility='public',
|
||||
container_format='bare')
|
||||
self.gettempdir.assert_not_called()
|
||||
|
||||
def test_create_ssh_key(self):
|
||||
|
||||
@@ -32,8 +32,37 @@ def basic_setup():
|
||||
"""
|
||||
|
||||
|
||||
def _get_default_glance_client():
|
||||
"""Create default Glance client using overcloud credentials."""
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
glance_client = openstack_utils.get_glance_session_client(keystone_session)
|
||||
return glance_client
|
||||
|
||||
|
||||
def get_stores_info(glance_client=None):
|
||||
"""Retrieve glance backing store info.
|
||||
|
||||
:param glance_client: Authenticated glanceclient
|
||||
:type glance_client: glanceclient.Client
|
||||
"""
|
||||
glance_client = glance_client or _get_default_glance_client()
|
||||
stores = glance_client.images.get_stores_info().get("stores", [])
|
||||
return stores
|
||||
|
||||
|
||||
def get_store_ids(glance_client=None):
|
||||
"""Retrieve glance backing store ids.
|
||||
|
||||
:param glance_client: Authenticated glanceclient
|
||||
:type glance_client: glanceclient.Client
|
||||
"""
|
||||
stores = get_stores_info(glance_client)
|
||||
return [store["id"] for store in stores]
|
||||
|
||||
|
||||
def add_image(image_url, glance_client=None, image_name=None, tags=[],
|
||||
properties=None):
|
||||
properties=None, backend=None, disk_format='qcow2',
|
||||
visibility='public', container_format='bare'):
|
||||
"""Retrieve image from ``image_url`` and add it to glance.
|
||||
|
||||
:param image_url: Retrievable URL with image data
|
||||
@@ -47,10 +76,14 @@ def add_image(image_url, glance_client=None, image_name=None, tags=[],
|
||||
:param properties: Properties to add to image
|
||||
:type properties: dict
|
||||
"""
|
||||
if not glance_client:
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
glance_client = openstack_utils.get_glance_session_client(
|
||||
keystone_session)
|
||||
glance_client = glance_client or _get_default_glance_client()
|
||||
if backend is not None:
|
||||
stores = get_store_ids(glance_client)
|
||||
if backend not in stores:
|
||||
raise ValueError("Invalid backend: %(backend)s "
|
||||
"(available: %(available)s)" % {
|
||||
"backend": backend,
|
||||
"available": ", ".join(stores)})
|
||||
if image_name:
|
||||
image = openstack_utils.get_images_by_name(
|
||||
glance_client, image_name)
|
||||
@@ -65,7 +98,11 @@ def add_image(image_url, glance_client=None, image_name=None, tags=[],
|
||||
image_url,
|
||||
image_name,
|
||||
tags=tags,
|
||||
properties=properties)
|
||||
properties=properties,
|
||||
backend=backend,
|
||||
disk_format=disk_format,
|
||||
visibility=visibility,
|
||||
container_format=container_format)
|
||||
|
||||
|
||||
def add_cirros_image(glance_client=None, image_name=None):
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Copyright 2020 Canonical Ltd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Collection of code for setting up and testing ironic."""
|
||||
@@ -0,0 +1,167 @@
|
||||
# Copyright 2020 Canonical Ltd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Code for configuring ironic."""
|
||||
|
||||
import copy
|
||||
import os
|
||||
|
||||
import zaza.openstack.charm_tests.glance.setup as glance_setup
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
from zaza.openstack.utilities import (
|
||||
cli as cli_utils,
|
||||
)
|
||||
import zaza.model as zaza_model
|
||||
|
||||
|
||||
FLAVORS = {
|
||||
'bm1.small': {
|
||||
'flavorid': 2,
|
||||
'ram': 2048,
|
||||
'disk': 20,
|
||||
'vcpus': 1,
|
||||
'properties': {
|
||||
"resources:CUSTOM_BAREMETAL1_SMALL": 1,
|
||||
},
|
||||
},
|
||||
'bm1.medium': {
|
||||
'flavorid': 3,
|
||||
'ram': 4096,
|
||||
'disk': 40,
|
||||
'vcpus': 2,
|
||||
'properties': {
|
||||
"resources:CUSTOM_BAREMETAL1_MEDIUM": 1,
|
||||
},
|
||||
},
|
||||
'bm1.large': {
|
||||
'flavorid': 4,
|
||||
'ram': 8192,
|
||||
'disk': 40,
|
||||
'vcpus': 4,
|
||||
'properties': {
|
||||
"resources:CUSTOM_BAREMETAL1_LARGE": 1,
|
||||
},
|
||||
},
|
||||
'bm1.tempest': {
|
||||
'flavorid': 6,
|
||||
'ram': 256,
|
||||
'disk': 1,
|
||||
'vcpus': 1,
|
||||
'properties': {
|
||||
"resources:CUSTOM_BAREMETAL1_TEMPEST": 1,
|
||||
},
|
||||
},
|
||||
'bm2.tempest': {
|
||||
'flavorid': 7,
|
||||
'ram': 512,
|
||||
'disk': 1,
|
||||
'vcpus': 1,
|
||||
'properties': {
|
||||
"resources:CUSTOM_BAREMETAL2_TEMPEST": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def add_ironic_deployment_image(initrd_url=None, kernel_url=None):
|
||||
"""Add Ironic deploy images to glance.
|
||||
|
||||
:param initrd_url: URL where the ari image resides
|
||||
:type initrd_url: str
|
||||
:param kernel_url: URL where the aki image resides
|
||||
:type kernel_url: str
|
||||
"""
|
||||
base_name = 'ironic-deploy'
|
||||
initrd_name = "{}-initrd".format(base_name)
|
||||
vmlinuz_name = "{}-vmlinuz".format(base_name)
|
||||
if not initrd_url:
|
||||
initrd_url = os.environ.get('TEST_IRONIC_DEPLOY_INITRD', None)
|
||||
if not kernel_url:
|
||||
kernel_url = os.environ.get('TEST_IRONIC_DEPLOY_VMLINUZ', None)
|
||||
if not all([initrd_url, kernel_url]):
|
||||
raise ValueError("Missing required deployment image URLs")
|
||||
glance_setup.add_image(
|
||||
initrd_url,
|
||||
image_name=initrd_name,
|
||||
backend="swift",
|
||||
disk_format="ari",
|
||||
container_format="ari")
|
||||
glance_setup.add_image(
|
||||
kernel_url,
|
||||
image_name=vmlinuz_name,
|
||||
backend="swift",
|
||||
disk_format="aki",
|
||||
container_format="aki")
|
||||
|
||||
|
||||
def add_ironic_os_image(image_url=None):
|
||||
"""Upload the operating system images built for bare metal deployments.
|
||||
|
||||
:param image_url: URL where the image resides
|
||||
:type image_url: str
|
||||
"""
|
||||
image_url = image_url or os.environ.get(
|
||||
'TEST_IRONIC_RAW_BM_IMAGE', None)
|
||||
image_name = "baremetal-ubuntu-image"
|
||||
if image_url is None:
|
||||
raise ValueError("Missing image_url")
|
||||
|
||||
glance_setup.add_image(
|
||||
image_url,
|
||||
image_name=image_name,
|
||||
backend="swift",
|
||||
disk_format="raw",
|
||||
container_format="bare")
|
||||
|
||||
|
||||
def set_temp_url_secret():
|
||||
"""Run the set-temp-url-secret on the ironic-conductor leader.
|
||||
|
||||
This is needed if direct boot method is enabled.
|
||||
"""
|
||||
zaza_model.run_action_on_leader(
|
||||
'ironic-conductor',
|
||||
'set-temp-url-secret',
|
||||
action_params={})
|
||||
|
||||
|
||||
def create_bm_flavors(nova_client=None):
|
||||
"""Create baremetal flavors.
|
||||
|
||||
:param nova_client: Authenticated nova client
|
||||
:type nova_client: novaclient.v2.client.Client
|
||||
"""
|
||||
if not nova_client:
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
nova_client = openstack_utils.get_nova_session_client(
|
||||
keystone_session)
|
||||
cli_utils.setup_logging()
|
||||
names = [flavor.name for flavor in nova_client.flavors.list()]
|
||||
# Disable scheduling based on standard flavor properties
|
||||
default_properties = {
|
||||
"resources:VCPU": 0,
|
||||
"resources:MEMORY_MB": 0,
|
||||
"resources:DISK_GB": 0,
|
||||
}
|
||||
for flavor in FLAVORS.keys():
|
||||
if flavor not in names:
|
||||
properties = copy.deepcopy(default_properties)
|
||||
properties.update(FLAVORS[flavor]["properties"])
|
||||
bm_flavor = nova_client.flavors.create(
|
||||
name=flavor,
|
||||
ram=FLAVORS[flavor]['ram'],
|
||||
vcpus=FLAVORS[flavor]['vcpus'],
|
||||
disk=FLAVORS[flavor]['disk'],
|
||||
flavorid=FLAVORS[flavor]['flavorid'])
|
||||
bm_flavor.set_keys(properties)
|
||||
@@ -0,0 +1,83 @@
|
||||
# Copyright 2020 Canonical Ltd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Encapsulate ironic testing."""
|
||||
|
||||
import logging
|
||||
|
||||
import ironicclient.client as ironic_client
|
||||
import zaza.openstack.charm_tests.test_utils as test_utils
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
|
||||
|
||||
def _get_ironic_client(ironic_api_version="1.58"):
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
ironic = ironic_client.Client(1, session=keystone_session,
|
||||
os_ironic_api_version=ironic_api_version)
|
||||
return ironic
|
||||
|
||||
|
||||
class IronicTest(test_utils.OpenStackBaseTest):
|
||||
"""Run Ironic specific tests."""
|
||||
|
||||
_SERVICES = ['ironic-api']
|
||||
|
||||
def test_110_catalog_endpoints(self):
|
||||
"""Verify that the endpoints are present in the catalog."""
|
||||
overcloud_auth = openstack_utils.get_overcloud_auth()
|
||||
keystone_client = openstack_utils.get_keystone_client(
|
||||
overcloud_auth)
|
||||
actual_endpoints = keystone_client.service_catalog.get_endpoints()
|
||||
actual_interfaces = [endpoint['interface'] for endpoint in
|
||||
actual_endpoints["baremetal"]]
|
||||
for expected_interface in ('internal', 'admin', 'public'):
|
||||
assert(expected_interface in actual_interfaces)
|
||||
|
||||
def test_400_api_connection(self):
|
||||
"""Simple api calls to check service is up and responding."""
|
||||
ironic = _get_ironic_client()
|
||||
|
||||
logging.info('listing conductors')
|
||||
conductors = ironic.conductor.list()
|
||||
assert(len(conductors) > 0)
|
||||
|
||||
# By default, only IPMI HW type is enabled. iDrac and Redfish
|
||||
# can optionally be enabled
|
||||
drivers = ironic.driver.list()
|
||||
driver_names = [drv.name for drv in drivers]
|
||||
|
||||
expected = ['intel-ipmi', 'ipmi']
|
||||
for exp in expected:
|
||||
assert(exp in driver_names)
|
||||
assert(len(driver_names) == 2)
|
||||
|
||||
def test_900_restart_on_config_change(self):
|
||||
"""Checking restart happens on config change.
|
||||
|
||||
Change debug mode and assert that change propagates to the correct
|
||||
file and that services are restarted as a result
|
||||
"""
|
||||
self.restart_on_changed_debug_oslo_config_file(
|
||||
'/etc/ironic/ironic.conf', self._SERVICES)
|
||||
|
||||
def test_910_pause_resume(self):
|
||||
"""Run pause and resume tests.
|
||||
|
||||
Pause service and check services are stopped then resume and check
|
||||
they are started
|
||||
"""
|
||||
logging.info('Skipping pause resume test LP: #1886202...')
|
||||
return
|
||||
with self.pause_resume(self._SERVICES):
|
||||
logging.info("Testing pause resume")
|
||||
@@ -2060,7 +2060,8 @@ def delete_volume_backup(cinder, vol_backup_id):
|
||||
|
||||
|
||||
def upload_image_to_glance(glance, local_path, image_name, disk_format='qcow2',
|
||||
visibility='public', container_format='bare'):
|
||||
visibility='public', container_format='bare',
|
||||
backend=None):
|
||||
"""Upload the given image to glance and apply the given label.
|
||||
|
||||
:param glance: Authenticated glanceclient
|
||||
@@ -2086,7 +2087,7 @@ def upload_image_to_glance(glance, local_path, image_name, disk_format='qcow2',
|
||||
disk_format=disk_format,
|
||||
visibility=visibility,
|
||||
container_format=container_format)
|
||||
glance.images.upload(image.id, open(local_path, 'rb'))
|
||||
glance.images.upload(image.id, open(local_path, 'rb'), backend=backend)
|
||||
|
||||
resource_reaches_status(
|
||||
glance.images,
|
||||
@@ -2098,7 +2099,8 @@ def upload_image_to_glance(glance, local_path, image_name, disk_format='qcow2',
|
||||
|
||||
|
||||
def create_image(glance, image_url, image_name, image_cache_dir=None, tags=[],
|
||||
properties=None):
|
||||
properties=None, backend=None, disk_format='qcow2',
|
||||
visibility='public', container_format='bare'):
|
||||
"""Download the image and upload it to glance.
|
||||
|
||||
Download an image from image_url and upload it to glance labelling
|
||||
@@ -2132,7 +2134,10 @@ def create_image(glance, image_url, image_name, image_cache_dir=None, tags=[],
|
||||
if not os.path.exists(local_path):
|
||||
download_image(image_url, local_path)
|
||||
|
||||
image = upload_image_to_glance(glance, local_path, image_name)
|
||||
image = upload_image_to_glance(
|
||||
glance, local_path, image_name, backend=backend,
|
||||
disk_format=disk_format, visibility=visibility,
|
||||
container_format=container_format)
|
||||
for tag in tags:
|
||||
result = glance.image_tags.update(image.id, tag)
|
||||
logging.debug(
|
||||
|
||||
Reference in New Issue
Block a user