Deploy templates: add API tests

Adds tests for the deploy templates API added in 1.55.

Also fixes an issue where the microversion fixture was not used in the resource
cleanup, so new resources would see a 404 and not be deleted. Fixing this
uncovered an issue in the volume tests where volume connectors and targets
could not be deleted due to being associated with a powered on node. The
simplest fix was to move node deletion before the volume connector and target
in resource cleanup.

Change-Id: I09d9a21e1ea5512c0140e818b0ca3de501870c12
Depends-On: https://review.openstack.org/631845
Story: 1722275
Task: 28679
This commit is contained in:
Mark Goddard 2019-02-15 12:23:34 +00:00 committed by Dmitry Tantsur
parent 94f4c20926
commit 6f2e72c455
3 changed files with 431 additions and 2 deletions

View File

@ -113,6 +113,11 @@ class BaremetalClient(base.BaremetalClient):
"""List all registered allocations."""
return self._list_request('allocations', **kwargs)
@base.handle_errors
def list_deploy_templates(self, **kwargs):
"""List all deploy templates."""
return self._list_request('deploy_templates', **kwargs)
@base.handle_errors
def show_node(self, uuid, api_version=None):
"""Gets a specific node.
@ -234,6 +239,14 @@ class BaremetalClient(base.BaremetalClient):
uri = '/nodes/%s/allocation' % node_ident
return self._show_request('nodes', uuid=None, uri=uri)
def show_deploy_template(self, deploy_template_ident):
"""Gets a specific deploy template.
:param deploy_template_ident: Name or UUID of deploy template.
:return: Serialized deploy template as a dictionary.
"""
return self._show_request('deploy_templates', deploy_template_ident)
@base.handle_errors
def create_node(self, chassis_id=None, **kwargs):
"""Create a baremetal node with the specified parameters.
@ -386,6 +399,26 @@ class BaremetalClient(base.BaremetalClient):
return self._create_request('volume/targets', volume_target)
@base.handle_errors
def create_deploy_template(self, name, **kwargs):
"""Create a deploy template with the specified parameters.
:param name: The name of the deploy template.
:param kwargs:
steps: deploy steps of the template.
uuid: UUID of the deploy template. Optional.
extra: meta-data of the deploy template. Optional.
:return: A tuple with the server response and the created deploy
template.
"""
deploy_template = {'name': name}
for arg in ('extra', 'steps', 'uuid'):
if arg in kwargs:
deploy_template[arg] = kwargs[arg]
return self._create_request('deploy_templates', deploy_template)
@base.handle_errors
def delete_node(self, uuid):
"""Deletes a node having the specified UUID.
@ -444,6 +477,15 @@ class BaremetalClient(base.BaremetalClient):
"""
return self._delete_request('volume/targets', volume_target_ident)
@base.handle_errors
def delete_deploy_template(self, deploy_template_ident):
"""Deletes a deploy template having the specified name or UUID.
:param deploy_template_ident: Name or UUID of the deploy template.
:return: A tuple with the server response and the response body.
"""
return self._delete_request('deploy_templates', deploy_template_ident)
@base.handle_errors
def update_node(self, uuid, patch=None, **kwargs):
"""Update the specified node.
@ -533,6 +575,20 @@ class BaremetalClient(base.BaremetalClient):
return self._patch_request('volume/targets', uuid, patch)
@base.handle_errors
def update_deploy_template(self, deploy_template_ident, patch):
"""Update the specified deploy template.
:param deploy_template_ident: Name or UUID of the deploy template.
:param patch: List of dicts representing json patches. Each dict
has keys 'path', 'op' and 'value'; to update a field.
:return: A tuple with the server response and the updated deploy
template.
"""
return self._patch_request('deploy_templates', deploy_template_ident,
patch)
@base.handle_errors
def set_node_power_state(self, node_uuid, state):
"""Set power state of the specified node.

View File

@ -19,6 +19,7 @@ from tempest import test
from ironic_tempest_plugin import clients
from ironic_tempest_plugin.common import waiters
from ironic_tempest_plugin.services.baremetal import base
from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
CONF = config.CONF
@ -33,8 +34,8 @@ SUPPORTED_DRIVERS = ['fake', 'fake-hardware']
# NOTE(jroll): resources must be deleted in a specific order, this list
# defines the resource types to clean up, and the correct order.
RESOURCE_TYPES = ['port', 'portgroup', 'volume_connector', 'volume_target',
'node', 'chassis']
RESOURCE_TYPES = ['port', 'portgroup', 'node', 'volume_connector',
'volume_target', 'chassis', 'deploy_template']
def creates(resource):
@ -110,6 +111,9 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
@classmethod
def resource_cleanup(cls):
"""Ensure that all created objects get destroyed."""
# Use the requested microversion for cleanup to ensure we can delete
# resources.
base.set_baremetal_api_microversion(cls.request_microversion)
try:
for node in cls.deployed_nodes:
try:
@ -130,6 +134,7 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
for u in uuids:
delete_method(u, ignore_errors=lib_exc.NotFound)
finally:
base.reset_baremetal_api_microversion()
super(BaseBaremetalTest, cls).resource_cleanup()
def _assertExpected(self, expected, actual):
@ -320,6 +325,19 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
return resp, body
@classmethod
@creates('deploy_template')
def create_deploy_template(cls, name, **kwargs):
"""Wrapper utility for creating test deploy template.
:param name: The name of the deploy template.
:return: A tuple with the server response and the created deploy
template.
"""
resp, body = cls.client.create_deploy_template(name=name, **kwargs)
return resp, body
@classmethod
def delete_chassis(cls, chassis_id):
"""Deletes a chassis having the specified UUID.
@ -411,6 +429,21 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
return resp
@classmethod
def delete_deploy_template(cls, deploy_template_ident):
"""Deletes a deploy template having the specified name or UUID.
:param deploy_template_ident: Name or UUID of the deploy template.
:return: Server response.
"""
resp, body = cls.client.delete_deploy_template(deploy_template_ident)
if deploy_template_ident in cls.created_objects['deploy_template']:
cls.created_objects['deploy_template'].remove(
deploy_template_ident)
return resp
def validate_self_link(self, resource, uuid, link):
"""Check whether the given self link formatted correctly."""
expected_link = "{base}/{pref}/{res}/{uuid}".format(

View File

@ -0,0 +1,340 @@
# 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 copy
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
from ironic_tempest_plugin.tests.api.admin import base
EXAMPLE_STEPS = [{
'interface': 'bios',
'step': 'apply_configuration',
'args': {},
'priority': 10
}]
def _get_random_trait():
return data_utils.rand_name('CUSTOM', '').replace('-', '_')
class TestDeployTemplates(base.BaseBaremetalTest):
"""Tests for deploy templates."""
min_microversion = '1.55'
def setUp(self):
super(TestDeployTemplates, self).setUp()
self.name = _get_random_trait()
self.steps = copy.deepcopy(EXAMPLE_STEPS)
_, self.template = self.create_deploy_template(self.name,
steps=self.steps)
@decorators.idempotent_id('8f39c794-4e6f-42fc-bfcb-e1f6eafff2e9')
def test_create_deploy_template_specifying_uuid(self):
name = _get_random_trait()
uuid = data_utils.rand_uuid()
_, template = self.create_deploy_template(name=name, steps=self.steps,
uuid=uuid)
_, body = self.client.show_deploy_template(uuid)
self._assertExpected(template, body)
@decorators.idempotent_id('7ac8fdd5-928b-4fbc-8341-3ebaad9def5e')
def test_delete_deploy_template(self):
self.delete_deploy_template(self.template['uuid'])
self.assertRaises(lib_exc.NotFound, self.client.show_deploy_template,
self.template['uuid'])
@decorators.idempotent_id('f424dd67-46c3-4169-b8f0-7e0c18b70437')
def test_show_deploy_template(self):
_, template = self.client.show_deploy_template(self.template['uuid'])
self._assertExpected(self.template, template)
self.assertIn('name', template)
self.assertIn('steps', template)
self.assertIn('uuid', template)
self.assertIn('extra', template)
@decorators.idempotent_id('2fd98e9a-10ce-405a-a32c-0d6079766183')
def test_show_deploy_template_with_links(self):
_, template = self.client.show_deploy_template(self.template['uuid'])
self.assertIn('links', template.keys())
self.assertEqual(2, len(template['links']))
self.assertIn(template['uuid'], template['links'][0]['href'])
@decorators.idempotent_id('cec2a01d-07af-4062-a8b0-9a1703f65bcf')
def test_list_deploy_templates(self):
_, body = self.client.list_deploy_templates()
self.assertIn(self.template['uuid'],
[i['uuid'] for i in body['deploy_templates']])
# Verify self links.
for template in body['deploy_templates']:
self.validate_self_link('deploy_templates', template['uuid'],
template['links'][0]['href'])
@decorators.idempotent_id('89aea2bf-c094-445f-b869-9fd56d1dfe5a')
def test_list_with_limit(self):
for i in range(2):
name = _get_random_trait()
self.create_deploy_template(name, steps=self.steps)
_, body = self.client.list_deploy_templates(limit=3)
next_marker = body['deploy_templates'][-1]['uuid']
self.assertIn(next_marker, body['next'])
@decorators.idempotent_id('c09c917e-c4d2-4148-9b6a-459bd126ed7c')
def test_list_deploy_templates_detail(self):
uuids = [
self.create_deploy_template(_get_random_trait(), steps=self.steps)
[1]['uuid'] for i in range(0, 5)]
_, body = self.client.list_deploy_templates(detail=True)
templates_dict = dict((template['uuid'], template)
for template in body['deploy_templates']
if template['uuid'] in uuids)
for uuid in uuids:
self.assertIn(uuid, templates_dict)
template = templates_dict[uuid]
self.assertIn('name', template)
self.assertIn('steps', template)
self.assertIn('uuid', template)
self.assertIn('extra', template)
# Verify self link.
self.validate_self_link('deploy_templates', template['uuid'],
template['links'][0]['href'])
@decorators.idempotent_id('a6cf1ade-e19a-41e2-b151-13ecf0d8f08c')
def test_update_deploy_template_replace(self):
new_name = _get_random_trait()
new_steps = [{
'interface': 'raid',
'step': 'create_configuration',
'args': {},
'priority': 10,
}]
patch = [{'path': '/name', 'op': 'replace', 'value': new_name},
{'path': '/steps', 'op': 'replace', 'value': new_steps}]
self.client.update_deploy_template(self.template['uuid'], patch)
_, body = self.client.show_deploy_template(self.template['uuid'])
self.assertEqual(new_name, body['name'])
self.assertEqual(new_steps, body['steps'])
@decorators.idempotent_id('bb168c63-452b-4065-9a81-77853ca9540a')
def test_update_deploy_template_add(self):
new_steps = [
{
'interface': 'bios',
'step': 'cache_bios_settings',
'args': {},
'priority': 20
},
{
'interface': 'bios',
'step': 'factory_reset',
'args': {},
'priority': 30
},
]
patch = [{'path': '/steps/1', 'op': 'add', 'value': new_steps[0]},
{'path': '/steps/2', 'op': 'add', 'value': new_steps[1]}]
self.client.update_deploy_template(self.template['uuid'], patch)
_, body = self.client.show_deploy_template(self.template['uuid'])
self.assertEqual(self.steps + new_steps, body['steps'])
@decorators.idempotent_id('2aa204a2-1d50-48fd-8b76-d2ed15586d50')
def test_update_deploy_template_mixed_ops(self):
new_name = _get_random_trait()
new_steps = [
{
'interface': 'bios',
'step': 'apply_configuration',
'args': {},
'priority': 20
},
{
'interface': 'bios',
'step': 'apply_configuration',
'args': {},
'priority': 30
},
]
patch = [{'path': '/name', 'op': 'replace', 'value': new_name},
{'path': '/steps/0', 'op': 'replace', 'value': new_steps[0]},
{'path': '/steps/0', 'op': 'remove'},
{'path': '/steps/0', 'op': 'add', 'value': new_steps[1]}]
self.client.update_deploy_template(self.template['uuid'], patch)
_, body = self.client.show_deploy_template(self.template['uuid'])
self.assertEqual(new_name, body['name'])
self.assertEqual([new_steps[1]], body['steps'])
class TestDeployTemplatesOldAPI(base.BaseBaremetalTest):
"""Negative tests for deploy templates using an old API version."""
max_microversion = '1.54'
def setUp(self):
super(TestDeployTemplatesOldAPI, self).setUp()
self.useFixture(
api_microversion_fixture.APIMicroversionFixture(
self.max_microversion)
)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e9481a0d-23e0-4757-bc11-c3c9ab9d3839')
def test_create_deploy_template_old_api(self):
self.assertRaises(lib_exc.NotFound,
self.create_deploy_template,
name=_get_random_trait(), steps=EXAMPLE_STEPS)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0d3af2aa-ba53-4c8a-92d4-91f9b4179fe7')
def test_update_deploy_template_old_api(self):
patch = [{'path': '/name', 'op': 'replace',
'value': _get_random_trait()}]
self.assertRaises(lib_exc.NotFound,
self.client.update_deploy_template,
_get_random_trait(), patch)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1646b1e5-ab81-45a8-9ea0-30444a4dcaa2')
def test_delete_deploy_template_old_api(self):
self.assertRaises(lib_exc.NotFound,
self.client.delete_deploy_template,
_get_random_trait())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('819480ac-f36a-4402-b1d5-504d7cf55b1f')
def test_list_deploy_templates_old_api(self):
self.assertRaises(lib_exc.NotFound,
self.client.list_deploy_templates)
class TestDeployTemplatesNegative(base.BaseBaremetalTest):
"""Negative tests for deploy templates."""
min_microversion = '1.55'
def setUp(self):
super(TestDeployTemplatesNegative, self).setUp()
self.useFixture(
api_microversion_fixture.APIMicroversionFixture(
self.min_microversion)
)
self.steps = EXAMPLE_STEPS
@decorators.attr(type=['negative'])
@decorators.idempotent_id('a4085c08-e718-4c2f-a796-0e115b659243')
def test_create_deploy_template_invalid_name(self):
name = 'invalid-name'
self.assertRaises(lib_exc.BadRequest,
self.create_deploy_template, name=name,
steps=self.steps)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('6390acc4-9490-4b23-8b4c-41888a78c9b7')
def test_create_deploy_template_duplicated_deploy_template_name(self):
name = _get_random_trait()
self.create_deploy_template(name=name, steps=self.steps)
self.assertRaises(lib_exc.Conflict, self.create_deploy_template,
name=name, steps=self.steps)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ed3f0cec-13e8-4175-9fdb-d129e7b7fe10')
def test_create_deploy_template_no_mandatory_field_name(self):
self.assertRaises(lib_exc.BadRequest, self.create_deploy_template,
name=None, steps=self.steps)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('af5dd0df-d903-463f-9535-9e4e9d6fd576')
def test_create_deploy_template_no_mandatory_field_steps(self):
self.assertRaises(lib_exc.BadRequest, self.create_deploy_template,
name=_get_random_trait())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('cbd33bc5-7602-40b7-943e-3e92217567a3')
def test_create_deploy_template_malformed_steps(self):
steps = {'key': 'value'}
self.assertRaises(lib_exc.BadRequest, self.create_deploy_template,
name=_get_random_trait(), steps=steps)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2a562fca-f377-4a6e-b332-37ee82d3a983')
def test_create_deploy_template_malformed_deploy_template_uuid(self):
uuid = 'malformed:uuid'
self.assertRaises(lib_exc.BadRequest, self.create_deploy_template,
name=_get_random_trait(), steps=self.steps,
uuid=uuid)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2c006994-88ca-43b7-b605-897d479229d9')
def test_show_deploy_template_nonexistent(self):
self.assertRaises(lib_exc.NotFound, self.client.show_deploy_template,
data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5a815f37-f015-4d68-9b22-099504f74805')
def test_update_deploy_template_remove_mandatory_field_steps(self):
name = _get_random_trait()
_, template = self.create_deploy_template(name=name, steps=self.steps)
# Removing one item from the collection
self.assertRaises(lib_exc.BadRequest,
self.client.update_deploy_template,
template['uuid'],
[{'path': '/steps/0', 'op': 'remove'}])
# Removing the collection
self.assertRaises(lib_exc.BadRequest,
self.client.update_deploy_template,
template['uuid'],
[{'path': '/steps', 'op': 'remove'}])
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ee852ebb-a601-4593-9d59-063fcbc8f964')
def test_update_deploy_template_remove_mandatory_field_name(self):
name = _get_random_trait()
_, template = self.create_deploy_template(name=name, steps=self.steps)
self.assertRaises(lib_exc.BadRequest,
self.client.update_deploy_template,
template['uuid'],
[{'path': '/name', 'op': 'remove'}])
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e59bf38d-272f-4490-b21e-9db217f11378')
def test_update_deploy_template_replace_empty_name(self):
name = _get_random_trait()
_, template = self.create_deploy_template(name=name, steps=self.steps)
self.assertRaises(lib_exc.BadRequest,
self.client.update_deploy_template,
template['uuid'],
[{'path': '/name', 'op': 'replace', 'value': ''}])