diff --git a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py index db06a9eb..628a0758 100644 --- a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py +++ b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py @@ -261,7 +261,7 @@ class BaremetalClient(base.BaremetalClient): """ node = {} - for field in ('resource_class', 'name', 'description'): + for field in ('resource_class', 'name', 'description', 'shard'): if kwargs.get(field): node[field] = kwargs[field] @@ -518,7 +518,8 @@ class BaremetalClient(base.BaremetalClient): # TODO(dtantsur): maintenance is set differently # in newer API versions. 'maintenance', - 'description') + 'description', + 'shard') if not patch: patch = self._make_patch(node_attributes, **kwargs) diff --git a/ironic_tempest_plugin/tests/api/admin/test_shards.py b/ironic_tempest_plugin/tests/api/admin/test_shards.py new file mode 100644 index 00000000..ffe69149 --- /dev/null +++ b/ironic_tempest_plugin/tests/api/admin/test_shards.py @@ -0,0 +1,128 @@ +# 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. + +from tempest import config +from tempest.lib import decorators + +from ironic_tempest_plugin.tests.api import base + +CONF = config.CONF + + +class TestAddShardsToNode(base.BaseBaremetalTest): + """Tests for baremetal shards.""" + + min_microversion = '1.82' + + def setUp(self): + super(TestAddShardsToNode, self).setUp() + # set a minimum API version + _, self.chassis = self.create_chassis() + + @decorators.idempotent_id('6f1e241d-4386-4730-b9ff-28c6a3dcad31') + def test_add_shard_to_node_at_create(self): + shard = 'at-create' + + _, body = self.create_node(self.chassis['uuid'], shard=shard) + self.assertEqual(shard, body['shard']) + + @decorators.idempotent_id('2eb91d29-e0a5-472b-aeb8-ef6d98eb0f3c') + def test_add_shard_to_node_post_create(self): + shard = 'post-create' + + _, node = self.create_node(self.chassis['uuid']) + _, before = self.client.show_node(node['uuid']) + self.assertIsNone(before['shard']) + + self.client.update_node(node['uuid'], shard=shard) + + _, after = self.client.show_node(node['uuid']) + self.assertEqual(shard, after['shard']) + + +class TestNodeShardQueries(base.BaseBaremetalTest): + """Tests for baremetal shards.""" + + min_microversion = '1.82' + + def setUp(self): + super(TestNodeShardQueries, self).setUp() + _, self.chassis = self.create_chassis() + _, bad_node = self.create_node(self.chassis['uuid'], shard='bad') + _, none_node = self.create_node(self.chassis['uuid']) # shard=None + self.bad_node_id = bad_node['uuid'] + self.none_node_id = none_node['uuid'] + + def _setup_nodes(self, good_shard, num=2): + good_node_ids = [] + for i in range(num): + _, node = self.create_node(self.chassis['uuid'], shard=good_shard) + good_node_ids.append(node['uuid']) + + return good_node_ids + + @decorators.idempotent_id('df74c989-6972-4104-a8d6-bd8e8d811353') + def test_show_all_nodes(self): + """Validate unfiltered API query will return nodes with a shard.""" + shard = "oneshardtest" + good_node_ids = self._setup_nodes(shard) + + _, fetched_nodes = self.client.list_nodes() + fetched_node_ids = [node['uuid'] for node in fetched_nodes['nodes']] + + for node_id in good_node_ids: + self.assertIn(node_id, fetched_node_ids) + + @decorators.idempotent_id('6f1e241d-4386-4730-b9ff-28c6a3dcad31') + def test_only_show_requested_shard(self): + """Validate filtering nodes by shard.""" + shard = "oneshardtest" + good_node_ids = self._setup_nodes(shard) + + _, fetched_nodes = self.client.list_nodes(shard=shard) + fetched_node_ids = [node['uuid'] for node in fetched_nodes['nodes']] + + self.assertItemsEqual(good_node_ids, fetched_node_ids) + + @decorators.idempotent_id('6f1e241d-4386-4730-b9ff-28c6a3dcad31') + def test_only_show_multiple_requested_shards(self): + """Validate filtering nodes by multiple shards.""" + shard = "multishardtest" + second_shard = "multishardtest2" + good_node_ids = self._setup_nodes(shard) + _, second_shard_node = self.create_node( + self.chassis['uuid'], shard=second_shard) + good_node_ids.append(second_shard_node['uuid']) + + _, fetched_nodes = self.client.list_nodes( + shard=','.join([shard, second_shard])) + fetched_node_ids = [node['uuid'] for node in fetched_nodes['nodes']] + + self.assertItemsEqual(good_node_ids, fetched_node_ids) + self.assertNotIn(self.bad_node_id, fetched_node_ids) + self.assertNotIn(self.none_node_id, fetched_node_ids) + + @decorators.idempotent_id('f7a2eeb7-d16e-480c-b698-3448491c73a1') + def test_show_sharded_nodes(self): + _, fetched_nodes = self.client.list_nodes(sharded=True) + fetched_node_ids = [node['uuid'] for node in fetched_nodes['nodes']] + + # NOTE(JayF): All other nodes under test are sharded + self.assertNotIn(self.none_node_id, fetched_node_ids) + + @decorators.idempotent_id('f7a2eeb7-d16e-480c-b698-3448491c73a1') + def test_show_unsharded_nodes(self): + _, fetched_nodes = self.client.list_nodes(sharded=False) + fetched_node_ids = [node['uuid'] for node in fetched_nodes['nodes']] + + self.assertIn(self.none_node_id, fetched_node_ids) + self.assertNotIn(self.bad_node_id, fetched_node_ids)