Allocation API: functional tests

This change adds tests on allocation API against fake nodes.

Change-Id: I9f750fe9088e4dda3d5d95cd8905101046ce71d1
Depends-On: https://review.openstack.org/636110
Story: #2004341
Task: #29411
This commit is contained in:
Dmitry Tantsur 2019-02-08 17:24:46 +01:00
parent 36960a98be
commit 47ff489dd2
4 changed files with 298 additions and 5 deletions

View File

@ -110,3 +110,37 @@ def wait_node_instance_association(client, instance_uuid, timeout=None,
'%(instance_uuid)s within the required time (%(timeout)s s).'
% {'instance_uuid': instance_uuid, 'timeout': timeout})
raise lib_exc.TimeoutException(msg)
def wait_for_allocation(client, allocation_ident, timeout=15, interval=1,
expect_error=False):
"""Wait for the allocation to become active.
:param client: an instance of tempest plugin BaremetalClient.
:param allocation_ident: UUID or name of the allocation.
:param timeout: the timeout after which the allocation is considered as
failed. Defaults to 15 seconds.
:param interval: an interval between show_allocation calls.
Defaults to 1 second.
:param expect_error: if True, return successfully even in case of an error.
"""
result = [None] # a mutable object to modify in the closure
def check():
result[0] = client.show_allocation(allocation_ident)
allocation = result[0][1]
if allocation['state'] == 'error' and not expect_error:
raise lib_exc.TempestException(
"Allocation %(ident)s failed: %(error)s" %
{'ident': allocation_ident,
'error': allocation.get('last_error')})
else:
return allocation['state'] != 'allocating'
if not test_utils.call_until_true(check, timeout, interval):
msg = ('Timed out waiting for the allocation %s to become active' %
allocation_ident)
raise lib_exc.TimeoutException(msg)
return result[0]

View File

@ -108,6 +108,11 @@ class BaremetalClient(base.BaremetalClient):
"""List all registered conductors."""
return self._list_request('conductors', **kwargs)
@base.handle_errors
def list_allocations(self, **kwargs):
"""List all registered allocations."""
return self._list_request('allocations', **kwargs)
@base.handle_errors
def show_node(self, uuid, api_version=None):
"""Gets a specific node.
@ -212,6 +217,23 @@ class BaremetalClient(base.BaremetalClient):
"""
return self._show_request('conductors', hostname)
def show_allocation(self, allocation_ident):
"""Gets a specific allocation.
:param allocation_ident: UUID or name of allocation.
:return: Serialized allocation as a dictionary.
"""
return self._show_request('allocations', allocation_ident)
def show_node_allocation(self, node_ident):
"""Gets an allocation for the node.
:param node_ident: Node UUID or name.
:return: Serialized allocation as a dictionary.
"""
uri = '/nodes/%s/allocation' % node_ident
return self._show_request('nodes', uuid=None, uri=uri)
@base.handle_errors
def create_node(self, chassis_id=None, **kwargs):
"""Create a baremetal node with the specified parameters.
@ -226,8 +248,9 @@ class BaremetalClient(base.BaremetalClient):
"""
node = {}
if kwargs.get('resource_class'):
node['resource_class'] = kwargs['resource_class']
for field in ('resource_class', 'name'):
if kwargs.get(field):
node[field] = kwargs[field]
node.update(
{'chassis_uuid': chassis_id,
@ -761,3 +784,25 @@ class BaremetalClient(base.BaremetalClient):
(node_uuid, trait), {})
self.expected_success(http_client.NO_CONTENT, resp.status)
return resp, body
@base.handle_errors
def create_allocation(self, resource_class, **kwargs):
"""Create a baremetal allocation with the specified parameters.
:param resource_class: Resource class to request.
:param kwargs: Other fields to pass.
:return: A tuple with the server response and the created allocation.
"""
kwargs['resource_class'] = resource_class
return self._create_request('allocations', kwargs)
@base.handle_errors
def delete_allocation(self, allocation_ident):
"""Deletes an allocation.
:param allocation_ident: UUID or name of the allocation.
:return: A tuple with the server response and the response body.
"""
return self._delete_request('allocations', allocation_ident)

View File

@ -118,6 +118,12 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
except lib_exc.BadRequest:
pass
for node in cls.created_objects['node']:
try:
cls.client.update_node(node, instance_uuid=None)
except lib_exc.TempestException:
pass
for resource in RESOURCE_TYPES:
uuids = cls.created_objects[resource]
delete_method = getattr(cls.client, 'delete_%s' % resource)
@ -171,7 +177,7 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
@classmethod
@creates('node')
def create_node(cls, chassis_id, cpu_arch='x86_64', cpus=8, local_gb=10,
memory_mb=4096, resource_class=None):
memory_mb=4096, **kwargs):
"""Wrapper utility for creating test baremetal nodes.
:param chassis_id: The unique identifier of the chassis.
@ -179,7 +185,7 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
:param cpus: Number of CPUs. Default: 8.
:param local_gb: Disk size. Default: 10.
:param memory_mb: Available RAM. Default: 4096.
:param resource_class: Node resource class.
:param kwargs: Other optional node fields.
:return: A tuple with the server response and the created node.
"""
@ -187,7 +193,7 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
cpus=cpus, local_gb=local_gb,
memory_mb=memory_mb,
driver=cls.driver,
resource_class=resource_class)
**kwargs)
return resp, body

View File

@ -0,0 +1,208 @@
# 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 random
from tempest import config
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
from ironic_tempest_plugin.common import waiters
from ironic_tempest_plugin.tests.api.admin import base
CONF = config.CONF
class TestAllocations(base.BaseBaremetalTest):
"""Tests for baremetal allocations."""
min_microversion = '1.52'
def provide_node(self, node_id, cleaning_timeout=None):
super(TestAllocations, self).provide_node(node_id, cleaning_timeout)
# Force non-empty power state, otherwise allocation API won't pick it
self.client.set_node_power_state(node_id, 'power off')
def setUp(self):
super(TestAllocations, self).setUp()
# Generate a resource class to prevent parallel tests from clashing
# with each other.
self.resource_class = 'x-small-%d' % random.randrange(1024)
_, self.chassis = self.create_chassis()
_, self.node = self.create_node(self.chassis['uuid'],
resource_class=self.resource_class)
self.provide_node(self.node['uuid'])
@decorators.idempotent_id('9203ea28-3c61-4108-8498-22247b654ff6')
def test_create_show_allocation(self):
self.assertIsNone(self.node['allocation_uuid'])
_, body = self.client.create_allocation(self.resource_class)
uuid = body['uuid']
self.assertTrue(uuid)
self.assertEqual('allocating', body['state'])
self.assertEqual(self.resource_class, body['resource_class'])
self.assertIsNone(body['last_error'])
self.assertIsNone(body['node_uuid'])
_, body = waiters.wait_for_allocation(self.client, uuid)
self.assertEqual('active', body['state'])
self.assertEqual(self.resource_class, body['resource_class'])
self.assertIsNone(body['last_error'])
self.assertEqual(self.node['uuid'], body['node_uuid'])
_, body2 = self.client.show_node_allocation(body['node_uuid'])
self.assertEqual(body, body2)
_, node = self.client.show_node(self.node['uuid'])
self.assertEqual(uuid, node['allocation_uuid'])
@decorators.idempotent_id('eb074d06-e5f4-4fb4-b992-c9929db488ae')
def test_create_allocation_with_traits(self):
_, node2 = self.create_node(self.chassis['uuid'],
resource_class=self.resource_class)
self.client.set_node_traits(node2['uuid'], ['CUSTOM_MEOW'])
self.provide_node(node2['uuid'])
_, body = self.client.create_allocation(self.resource_class,
traits=['CUSTOM_MEOW'])
uuid = body['uuid']
self.assertTrue(uuid)
self.assertEqual('allocating', body['state'])
self.assertEqual(['CUSTOM_MEOW'], body['traits'])
self.assertIsNone(body['last_error'])
_, body = waiters.wait_for_allocation(self.client, uuid)
self.assertEqual('active', body['state'])
self.assertEqual(['CUSTOM_MEOW'], body['traits'])
self.assertIsNone(body['last_error'])
self.assertEqual(node2['uuid'], body['node_uuid'])
@decorators.idempotent_id('12d19297-f35a-408a-8b1e-3cd244e30abe')
def test_create_allocation_candidate_node(self):
node_name = 'allocation-test-1'
_, node2 = self.create_node(self.chassis['uuid'],
resource_class=self.resource_class,
name=node_name)
self.provide_node(node2['uuid'])
_, body = self.client.create_allocation(self.resource_class,
candidate_nodes=[node_name])
uuid = body['uuid']
self.assertTrue(uuid)
self.assertEqual('allocating', body['state'])
self.assertEqual([node2['uuid']], body['candidate_nodes'])
self.assertIsNone(body['last_error'])
_, body = waiters.wait_for_allocation(self.client, uuid)
self.assertEqual('active', body['state'])
self.assertEqual([node2['uuid']], body['candidate_nodes'])
self.assertIsNone(body['last_error'])
self.assertEqual(node2['uuid'], body['node_uuid'])
@decorators.idempotent_id('84eb3c21-4e16-4f33-9551-dce0f8689462')
def test_delete_allocation(self):
_, body = self.client.create_allocation(self.resource_class)
self.client.delete_allocation(body['uuid'])
self.assertRaises(lib_exc.NotFound, self.client.show_allocation,
body['uuid'])
@decorators.idempotent_id('5e30452d-ee92-4342-82c1-5eea5e55c937')
def test_delete_allocation_by_name(self):
_, body = self.client.create_allocation(self.resource_class,
name='banana')
self.client.delete_allocation('banana')
self.assertRaises(lib_exc.NotFound, self.client.show_allocation,
'banana')
@decorators.idempotent_id('fbbc13bc-86da-438b-af01-d1bc1bab57d6')
def test_show_by_name(self):
_, body = self.client.create_allocation(self.resource_class,
name='banana')
_, loaded_body = self.client.show_allocation('banana')
self._assertExpected(body, loaded_body)
@decorators.idempotent_id('4ca123c4-160d-4d8d-a3f7-15feda812263')
def test_list_allocations(self):
_, body = self.client.create_allocation(self.resource_class)
_, listing = self.client.list_allocations()
self.assertIn(body['uuid'],
[i['uuid'] for i in listing['allocations']])
_, listing = self.client.list_allocations(
resource_class=self.resource_class)
self.assertEqual([body['uuid']],
[i['uuid'] for i in listing['allocations']])
@decorators.idempotent_id('092b7148-9ff0-4107-be57-2cfcd21eb5d7')
def test_list_allocations_by_state(self):
_, body = self.client.create_allocation(self.resource_class)
_, body2 = self.client.create_allocation(self.resource_class + 'foo2')
waiters.wait_for_allocation(self.client, body['uuid'])
waiters.wait_for_allocation(self.client, body2['uuid'],
expect_error=True)
_, listing = self.client.list_allocations(state='active')
uuids = [i['uuid'] for i in listing['allocations']]
self.assertIn(body['uuid'], uuids)
self.assertNotIn(body2['uuid'], uuids)
_, listing = self.client.list_allocations(state='error')
uuids = [i['uuid'] for i in listing['allocations']]
self.assertNotIn(body['uuid'], uuids)
self.assertIn(body2['uuid'], uuids)
_, listing = self.client.list_allocations(state='allocating')
uuids = [i['uuid'] for i in listing['allocations']]
self.assertNotIn(body['uuid'], uuids)
self.assertNotIn(body2['uuid'], uuids)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('bf7e1375-019a-466a-a294-9c1052827ada')
def test_create_allocation_resource_class_mismatch(self):
_, body = self.client.create_allocation(self.resource_class + 'foo')
_, body = waiters.wait_for_allocation(self.client, body['uuid'],
expect_error=True)
self.assertEqual('error', body['state'])
self.assertTrue(body['last_error'])
@decorators.attr(type=['negative'])
@decorators.idempotent_id('b4eeddee-ca34-44f9-908b-490b78b18486')
def test_create_allocation_traits_mismatch(self):
_, body = self.client.create_allocation(
self.resource_class, traits=['CUSTOM_DOES_NOT_EXIST'])
_, body = waiters.wait_for_allocation(self.client, body['uuid'],
expect_error=True)
self.assertEqual('error', body['state'])
self.assertTrue(body['last_error'])
@decorators.attr(type=['negative'])
@decorators.idempotent_id('2378727f-77c3-4289-9562-bd2f3b147a60')
def test_create_allocation_node_mismatch(self):
_, node2 = self.create_node(self.chassis['uuid'],
resource_class=self.resource_class + 'alt')
# Mismatch between the resource class and the candidate node
_, body = self.client.create_allocation(
self.resource_class, candidate_nodes=[node2['uuid']])
_, body = waiters.wait_for_allocation(self.client, body['uuid'],
expect_error=True)
self.assertEqual('error', body['state'])
self.assertTrue(body['last_error'])