Add node trait tests

Adds admin API tests for the node traits API added in Bare Metal REST
API microversion 1.37.

Change-Id: Ied30a56c93c874d036ca82569321660d6aa23906
Partial-Bug: #1722194
Depends-On: I313fa01fbf20bf0ff19f102ea63b02e72ac2b856
This commit is contained in:
Mark Goddard 2018-01-19 15:08:21 +00:00
parent f994081ddf
commit bcdb740e5d
2 changed files with 377 additions and 0 deletions

View File

@ -640,3 +640,58 @@ class BaremetalClient(base.BaremetalClient):
resp, body = self.get(uri)
self.expected_success(200, resp.status)
return resp, self.deserialize(body)
@base.handle_errors
def list_node_traits(self, node_uuid):
"""List all traits associated with the node.
:param node_uuid: The unique identifier of the node.
"""
return self._list_request('/nodes/%s/traits' % node_uuid)
@base.handle_errors
def set_node_traits(self, node_uuid, traits):
"""Set all traits of the specified node.
:param node_uuid: The unique identifier of the node.
:param traits: A list of traits to set.
"""
request = {'traits': traits}
resp, body = self._put_request('nodes/%s/traits' %
node_uuid, request)
self.expected_success(http_client.NO_CONTENT, resp.status)
return resp, body
@base.handle_errors
def add_node_trait(self, node_uuid, trait):
"""Add a trait to the specified node.
:param node_uuid: The unique identifier of the node.
:param trait: A trait to add.
"""
resp, body = self._put_request('nodes/%s/traits/%s' %
(node_uuid, trait), {})
self.expected_success(http_client.NO_CONTENT, resp.status)
return resp, body
@base.handle_errors
def remove_node_traits(self, node_uuid):
"""Remove all traits from the specified node.
:param node_uuid: Unique identifier of the node in UUID format.
"""
resp, body = self._delete_request('nodes/%s/traits' % node_uuid, {})
self.expected_success(http_client.NO_CONTENT, resp.status)
return resp, body
@base.handle_errors
def remove_node_trait(self, node_uuid, trait):
"""Remove a trait from the specified node.
:param node_uuid: Unique identifier of the node in UUID format.
:param trait: A trait to remove.
"""
resp, body = self._delete_request('nodes/%s/traits/%s' %
(node_uuid, trait), {})
self.expected_success(http_client.NO_CONTENT, resp.status)
return resp, body

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import uuidutils
import six
from tempest import config
from tempest.lib.common.utils import data_utils
@ -401,3 +402,324 @@ class TestNodesVif(base.BaseBaremetalTest):
self.node['uuid'], self.nport_id)
self.client.vif_detach(self.node['uuid'], self.nport_id)
class TestNodesTraits(base.BaseBaremetalTest):
min_microversion = '1.37'
def setUp(self):
super(TestNodesTraits, self).setUp()
self.useFixture(
api_microversion_fixture.APIMicroversionFixture(
TestNodesTraits.min_microversion)
)
_, self.chassis = self.create_chassis()
# One standard trait & one custom trait.
self.traits = ['CUSTOM_TRAIT1', 'HW_CPU_X86_VMX']
_, self.node = self.create_node(self.chassis['uuid'])
@decorators.idempotent_id('5c3a2dd0-af10-474d-a209-d30426e1eb5d')
def test_list_node_traits(self):
"""List traits for a node."""
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual([], body['traits'])
self.client.set_node_traits(self.node['uuid'], self.traits)
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual(self.traits, body['traits'])
@decorators.attr(type='negative')
@decorators.idempotent_id('3b83dbd3-4a89-4173-920a-ca33ed3aad69')
def test_list_node_traits_non_existent_node(self):
"""Try to list traits for a non-existent node."""
node_uuid = uuidutils.generate_uuid()
self.assertRaises(
lib_exc.NotFound,
self.client.list_node_traits, node_uuid)
@decorators.idempotent_id('aa961bf6-ea2f-484b-961b-eae2da0e6b7e')
def test_set_node_traits(self):
"""Set the traits for a node."""
self.client.set_node_traits(self.node['uuid'], self.traits)
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual(self.traits, body['traits'])
self.client.set_node_traits(self.node['uuid'], [])
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual([], body['traits'])
@decorators.idempotent_id('727a5e11-5654-459f-8af6-e14eb987a283')
def test_set_node_traits_max_traits(self):
"""Set the maximum number of traits for a node."""
traits = ['CUSTOM_TRAIT%d' % i for i in range(50)]
self.client.set_node_traits(self.node['uuid'], traits)
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual(sorted(traits), sorted(body['traits']))
@decorators.attr(type='negative')
@decorators.idempotent_id('75831f5d-ca44-403b-8fd6-f7cad95b1c54')
def test_set_node_traits_too_many(self):
"""Set more than the maximum number of traits for a node."""
traits = ['CUSTOM_TRAIT%d' % i for i in range(51)]
self.assertRaises(
lib_exc.BadRequest,
self.client.set_node_traits, self.node['uuid'], traits)
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual([], body['traits'])
@decorators.idempotent_id('d81ceeab-a50f-427a-bc5a-aa916478d0d3')
def test_set_node_traits_duplicate_trait(self):
"""Set the traits for a node, ensuring duplicates are ignored."""
self.client.set_node_traits(self.node['uuid'],
['CUSTOM_TRAIT1', 'CUSTOM_TRAIT1'])
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual(['CUSTOM_TRAIT1'], body['traits'])
@decorators.attr(type='negative')
@decorators.idempotent_id('2fb4c9d9-8e5b-4189-b547-26596014491c')
def test_set_node_traits_non_existent_node(self):
"""Try to set traits for a non-existent node."""
node_uuid = uuidutils.generate_uuid()
self.assertRaises(
lib_exc.NotFound,
self.client.set_node_traits, node_uuid, ['CUSTOM_TRAIT1'])
@decorators.idempotent_id('47db09d9-af2b-424d-9d51-7efca2920f20')
def test_add_node_trait_long(self):
"""Add a node trait of the largest possible length."""
trait_long_name = 'CUSTOM_' + data_utils.arbitrary_string(248).upper()
self.client.add_node_trait(self.node['uuid'], trait_long_name)
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual([trait_long_name], body['traits'])
@decorators.attr(type='negative')
@decorators.idempotent_id('2a4daa8d-2b85-40ac-a8a0-0462cc9a57ef')
def test_add_node_trait_too_long(self):
"""Try to add a node trait longer than the largest possible length."""
trait_long_name = 'CUSTOM_' + data_utils.arbitrary_string(249).upper()
self.assertRaises(
lib_exc.BadRequest,
self.client.add_node_trait, self.node['uuid'], trait_long_name)
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual([], body['traits'])
@decorators.idempotent_id('4b737e7f-101e-493e-b5ce-494fbffe18fd')
def test_add_node_trait_duplicate_trait(self):
"""Add a node trait that already exists."""
self.client.add_node_trait(self.node['uuid'], 'CUSTOM_TRAIT1')
self.client.add_node_trait(self.node['uuid'], 'CUSTOM_TRAIT1')
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual(['CUSTOM_TRAIT1'], body['traits'])
@decorators.attr(type='negative')
@decorators.idempotent_id('65bce181-89ce-435e-a7d8-3ba60aafd08d')
def test_add_node_trait_too_many(self):
"""Add a trait to a node that would exceed the maximum."""
traits = ['CUSTOM_TRAIT%d' % i for i in range(50)]
self.client.set_node_traits(self.node['uuid'], traits)
self.assertRaises(
lib_exc.BadRequest,
self.client.add_node_trait, self.node['uuid'], 'CUSTOM_TRAIT50')
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual(sorted(traits), sorted(body['traits']))
@decorators.attr(type='negative')
@decorators.idempotent_id('cca0e831-32af-4ce9-bfce-d3834fea57aa')
def test_add_node_trait_non_existent_node(self):
"""Try to add a trait to a non-existent node."""
node_uuid = uuidutils.generate_uuid()
self.assertRaises(
lib_exc.NotFound,
self.client.add_node_trait, node_uuid, 'CUSTOM_TRAIT1')
@decorators.idempotent_id('e4bf8bf0-3004-44bc-8bfe-f9f1a167d999')
def test_remove_node_traits(self):
"""Remove all traits from a node."""
self.client.set_node_traits(self.node['uuid'], self.traits)
self.client.remove_node_traits(self.node['uuid'])
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual([], body['traits'])
@decorators.idempotent_id('4d8c9a35-0036-4139-85c1-5f242395680f')
def test_remove_node_traits_no_traits(self):
"""Remove all traits from a node that has no traits."""
self.client.remove_node_traits(self.node['uuid'])
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual([], body['traits'])
@decorators.attr(type='negative')
@decorators.idempotent_id('625c911a-48e8-4bef-810b-7cf33c0846a2')
def test_remove_node_traits_non_existent_node(self):
"""Try to remove all traits from a non-existent node."""
node_uuid = uuidutils.generate_uuid()
self.assertRaises(
lib_exc.NotFound,
self.client.remove_node_traits, node_uuid)
@decorators.idempotent_id('3591d514-39b9-425e-9afe-ea74ae347486')
def test_remove_node_trait(self):
"""Remove a trait from a node."""
self.client.set_node_traits(self.node['uuid'], self.traits)
self.client.remove_node_trait(self.node['uuid'], 'CUSTOM_TRAIT1')
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual(['HW_CPU_X86_VMX'], body['traits'])
@decorators.attr(type='negative')
@decorators.idempotent_id('b50ae543-5e5e-4b1a-b2f2-9e00fe55974b')
def test_remove_node_trait_non_existent_trait(self):
"""Try to remove a non-existent trait from a node."""
self.assertRaises(
lib_exc.NotFound,
self.client.remove_node_trait, self.node['uuid'], 'CUSTOM_TRAIT1')
@decorators.attr(type='negative')
@decorators.idempotent_id('f1469745-7cdf-4cae-9699-73d029c47bc3')
def test_remove_node_trait_non_existent_node(self):
"""Try to remove a trait from a non-existent node."""
node_uuid = uuidutils.generate_uuid()
self.assertRaises(
lib_exc.NotFound,
self.client.remove_node_trait, node_uuid, 'CUSTOM_TRAIT1')
@decorators.idempotent_id('03f9e57f-e584-448a-926f-53035e583e7e')
def test_list_nodes_detail(self):
"""Get detailed nodes list."""
self.client.set_node_traits(self.node['uuid'], self.traits)
_, body = self.client.list_nodes_detail()
self.assertGreaterEqual(len(body['nodes']), 1)
for node in body['nodes']:
self.assertIn('traits', node)
if node['uuid'] == self.node['uuid']:
self.assertEqual(self.traits, node['traits'])
@decorators.idempotent_id('2b82f704-1580-403a-af92-92c29a7eebb7')
def test_list_nodes_traits_field(self):
"""Get nodes list with the traits field."""
self.client.set_node_traits(self.node['uuid'], self.traits)
_, body = self.client.list_nodes(fields='uuid,traits')
self.assertGreaterEqual(len(body['nodes']), 1)
for node in body['nodes']:
self.assertIn('traits', node)
if node['uuid'] == self.node['uuid']:
self.assertEqual(self.traits, node['traits'])
@decorators.idempotent_id('c83c537a-76aa-4d8a-8673-128d01ee403d')
def test_show_node(self):
"""Show a node with traits."""
self.client.set_node_traits(self.node['uuid'], self.traits)
_, body = self.client.show_node(self.node['uuid'])
self.assertIn('traits', body)
self.assertEqual(self.traits, body['traits'])
@decorators.attr(type='negative')
@decorators.idempotent_id('9ab6a19c-83b9-4600-b55b-325a51e2f8f6')
def test_update_node_traits(self):
"""Try updating an existing node with traits."""
patch = [{'path': '/traits',
'op': 'add',
'value': ['CUSTOM_TRAIT1']}]
self.assertRaises(
lib_exc.BadRequest,
self.client.update_node, self.node['uuid'], patch)
_, body = self.client.list_node_traits(self.node['uuid'])
self.assertEqual([], body['traits'])
class TestNodesTraitsOldApi(base.BaseBaremetalTest):
def setUp(self):
super(TestNodesTraitsOldApi, self).setUp()
_, self.chassis = self.create_chassis()
_, self.node = self.create_node(self.chassis['uuid'])
@decorators.attr(type='negative')
@decorators.idempotent_id('5419af7b-4e27-4be4-88f6-e01c598a8102')
def test_list_node_traits_old_api(self):
"""Try to list traits for a node using an older api version."""
exc = self.assertRaises(
lib_exc.UnexpectedResponseCode,
self.client.list_node_traits, self.node['uuid'])
self.assertEqual(406, exc.resp.status)
@decorators.attr(type='negative')
@decorators.idempotent_id('a4353f3a-bedc-4579-9c7e-4bebcd95903d')
def test_add_node_trait_old_api(self):
"""Try to add a trait to a node using an older api version."""
exc = self.assertRaises(
lib_exc.UnexpectedResponseCode,
self.client.add_node_trait, self.node['uuid'], 'CUSTOM_TRAIT1')
self.assertEqual(405, exc.resp.status)
@decorators.attr(type='negative')
@decorators.idempotent_id('91cc43d8-2f6f-4b1b-95e9-68dedca54e6b')
def test_set_node_traits_old_api(self):
"""Try to set traits for a node using an older api version."""
exc = self.assertRaises(
lib_exc.UnexpectedResponseCode,
self.client.set_node_traits, self.node['uuid'], ['CUSTOM_TRAIT1'])
self.assertEqual(405, exc.resp.status)
@decorators.attr(type='negative')
@decorators.idempotent_id('0f9af890-a57a-4c25-86c8-6418d1b8f4d4')
def test_remove_node_trait_old_api(self):
"""Try to remove a trait from a node using an older api version."""
self.assertRaises(
lib_exc.NotFound,
self.client.remove_node_trait, self.node['uuid'], 'CUSTOM_TRAIT1')
@decorators.attr(type='negative')
@decorators.idempotent_id('f8375b3c-1939-4d1c-97c4-d23e10680090')
def test_remove_node_traits_old_api(self):
"""Try to remove all traits from a node using an older api version."""
self.assertRaises(
lib_exc.NotFound,
self.client.remove_node_traits, self.node['uuid'])
@decorators.attr(type='negative')
@decorators.idempotent_id('525eeb59-b7ce-413d-a37b-401e67402a4c')
def test_list_nodes_detail_old_api(self):
"""Get detailed nodes list, ensure they have no traits."""
_, body = self.client.list_nodes_detail()
self.assertGreaterEqual(len(body['nodes']), 1)
for node in body['nodes']:
self.assertNotIn('traits', node)
@decorators.attr(type='negative')
@decorators.idempotent_id('eb75b3c8-ac9c-4399-90a2-c0030bfde7a6')
def test_list_nodes_traits_field(self):
"""Try to list nodes' traits field using older api version."""
exc = self.assertRaises(
lib_exc.UnexpectedResponseCode,
self.client.list_nodes, fields='traits')
self.assertEqual(406, exc.resp.status)
@decorators.attr(type='negative')
@decorators.idempotent_id('214ae7fc-149b-4657-b6bc-66353d49ade8')
def test_show_node_old_api(self):
"""Show a node, ensure it has no traits."""
_, body = self.client.show_node(self.node['uuid'])
self.assertNotIn('traits', body)