From f2bed8f2531ec92b47303346e48fe84cdb92b1ec Mon Sep 17 00:00:00 2001 From: Sharpz7 Date: Fri, 1 Nov 2024 21:12:27 +0000 Subject: [PATCH] Support /v1/shards Unit Tests Boilerplate was generated by GPT-4o Generated-By: GPT-4o Change-Id: I6f45cc678a5931e3525fb85256c1608e75fe089f --- ironicclient/osc/v1/baremetal_shard.py | 36 ++++++ ironicclient/tests/unit/osc/v1/fakes.py | 8 ++ .../tests/unit/osc/v1/test_baremetal_shard.py | 57 +++++++++ ironicclient/tests/unit/v1/test_shard.py | 115 ++++++++++++++++++ ironicclient/v1/client.py | 2 + ironicclient/v1/resource_fields.py | 6 + ironicclient/v1/shard.py | 39 ++++++ setup.cfg | 1 + 8 files changed, 264 insertions(+) create mode 100644 ironicclient/osc/v1/baremetal_shard.py create mode 100644 ironicclient/tests/unit/osc/v1/test_baremetal_shard.py create mode 100644 ironicclient/tests/unit/v1/test_shard.py create mode 100644 ironicclient/v1/shard.py diff --git a/ironicclient/osc/v1/baremetal_shard.py b/ironicclient/osc/v1/baremetal_shard.py new file mode 100644 index 000000000..33c4ec8e9 --- /dev/null +++ b/ironicclient/osc/v1/baremetal_shard.py @@ -0,0 +1,36 @@ +# 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 logging + +from osc_lib.command import command +from osc_lib import utils as oscutils + +from ironicclient.v1 import resource_fields as res_fields + + +class ListBaremetalShard(command.Lister): + """List baremetal shards.""" + + log = logging.getLogger(__name__ + ".ListBaremetalShard") + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + client = self.app.client_manager.baremetal + + data = client.shard.list() + columns = res_fields.SHARD_RESOURCE.fields + labels = res_fields.SHARD_RESOURCE.labels + + return (labels, + (oscutils.get_item_properties(s, columns) for s in data)) diff --git a/ironicclient/tests/unit/osc/v1/fakes.py b/ironicclient/tests/unit/osc/v1/fakes.py index dbe79fb36..e2483855d 100644 --- a/ironicclient/tests/unit/osc/v1/fakes.py +++ b/ironicclient/tests/unit/osc/v1/fakes.py @@ -255,6 +255,14 @@ RUNBOOK = { 'steps': baremetal_runbook_steps, 'extra': baremetal_runbook_extra, } + +baremetal_shard_name = 'example_shard' +baremetal_shard_count = 47 +SHARD = { + 'name': baremetal_shard_name, + 'count': baremetal_shard_count, +} + NODE_HISTORY = [ { 'uuid': 'abcdef1', diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_shard.py b/ironicclient/tests/unit/osc/v1/test_baremetal_shard.py new file mode 100644 index 000000000..428b7e180 --- /dev/null +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_shard.py @@ -0,0 +1,57 @@ +# 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 ironicclient.osc.v1 import baremetal_shard +from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes + + +class TestBaremetalShard(baremetal_fakes.TestBaremetal): + def setUp(self): + super(TestBaremetalShard, self).setUp() + self.baremetal_mock = self.app.client_manager.baremetal + self.baremetal_mock.reset_mock() + + +class TestBaremetalShardList(TestBaremetalShard): + def setUp(self): + super(TestBaremetalShardList, self).setUp() + + # Return a list containing mocked shard data to + # simulate the expected output + self.baremetal_mock.shard.list.return_value = [ + baremetal_fakes.FakeBaremetalResource( + None, + copy.deepcopy(baremetal_fakes.SHARD), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = baremetal_shard.ListBaremetalShard(self.app, None) + + def test_shard_list(self): + arglist = [] + verifylist = [] + + # Parse arguments and invoke command + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Define expected columns and data output + collist = ("Name", "Count") + datalist = ((baremetal_fakes.baremetal_shard_name, + baremetal_fakes.baremetal_shard_count), ) + + self.assertEqual(collist, columns) + self.assertEqual(datalist, tuple(data)) diff --git a/ironicclient/tests/unit/v1/test_shard.py b/ironicclient/tests/unit/v1/test_shard.py new file mode 100644 index 000000000..0a31ad229 --- /dev/null +++ b/ironicclient/tests/unit/v1/test_shard.py @@ -0,0 +1,115 @@ +# 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 unittest +from unittest.mock import patch + +from ironicclient.v1.shard import ShardManager + + +class TestShardManager(unittest.TestCase): + + @patch('ironicclient.common.base.Manager._list') + def test_list_shards(self, mock_list): + # Mock response for the list of shards + mock_response = [ + {'name': 'example_shard1', 'count': 47}, + {'name': 'example_shard2', 'count': 46}, + {'name': None, 'count': 3} # Nodes with no shard assigned + ] + + # Configure mock to return the mocked response + mock_list.return_value = mock_response + + # Initialize the ShardManager + shard_manager = ShardManager(api=None) # `api=None` for simplicity + + # Perform the test call + result = shard_manager.list(os_ironic_api_version="1.82") + + # Assertions + mock_list.assert_called_once_with( + shard_manager._path(None), + "shards", + os_ironic_api_version="1.82", + global_request_id=None + ) + self.assertEqual(len(result), 3) + self.assertEqual(result[0]['name'], 'example_shard1') + self.assertEqual(result[0]['count'], 47) + self.assertEqual(result[1]['name'], 'example_shard2') + self.assertEqual(result[1]['count'], 46) + self.assertIsNone(result[2]['name']) + self.assertEqual(result[2]['count'], 3) + + @patch('ironicclient.common.base.Manager._list') + def test_list_shards_empty(self, mock_list): + # Test when the shards list is empty + mock_list.return_value = [] + + # Initialize the ShardManager + shard_manager = ShardManager(api=None) + + # Perform the test call + result = shard_manager.list(os_ironic_api_version="1.82") + + # Assertions + mock_list.assert_called_once_with( + shard_manager._path(None), + "shards", + os_ironic_api_version="1.82", + global_request_id=None + ) + self.assertEqual(result, []) + + @patch('ironicclient.common.base.Manager._list') + def test_list_shards_with_global_request_id(self, mock_list): + # Test with global request ID + mock_response = [ + {'name': 'example_shard1', 'count': 47}, + {'name': 'example_shard2', 'count': 46} + ] + mock_list.return_value = mock_response + + # Initialize the ShardManager + shard_manager = ShardManager(api=None) + + # Perform the test call with global_request_id + result = shard_manager.list( + os_ironic_api_version="1.82", + global_request_id="req-12345" + ) + + # Assertions + mock_list.assert_called_once_with( + shard_manager._path(None), + "shards", + os_ironic_api_version="1.82", + global_request_id="req-12345" + ) + self.assertEqual(result, mock_response) + + @patch('ironicclient.common.base.Manager._list') + def test_list_shards_api_version_mismatch(self, mock_list): + # Simulate a 404 error for an unsupported API version + mock_list.side_effect = ValueError( + "404 Not Found: The requested version is not supported" + ) + + # Initialize the ShardManager + shard_manager = ShardManager(api=None) + + # Perform the test call and assert exception is raised + with self.assertRaises(ValueError) as context: + shard_manager.list(os_ironic_api_version="1.50") + + self.assertIn("404 Not Found", str(context.exception)) diff --git a/ironicclient/v1/client.py b/ironicclient/v1/client.py index 9af8905de..d56643249 100644 --- a/ironicclient/v1/client.py +++ b/ironicclient/v1/client.py @@ -28,6 +28,7 @@ from ironicclient.v1 import node from ironicclient.v1 import port from ironicclient.v1 import portgroup from ironicclient.v1 import runbook +from ironicclient.v1 import shard from ironicclient.v1 import volume_connector from ironicclient.v1 import volume_target @@ -106,6 +107,7 @@ class Client(object): self.allocation = allocation.AllocationManager(self.http_client) self.deploy_template = deploy_template.DeployTemplateManager( self.http_client) + self.shard = shard.ShardManager(self.http_client) @property def current_api_version(self): diff --git a/ironicclient/v1/resource_fields.py b/ironicclient/v1/resource_fields.py index 5d278bec1..22fa2ee0f 100644 --- a/ironicclient/v1/resource_fields.py +++ b/ironicclient/v1/resource_fields.py @@ -47,6 +47,7 @@ class Resource(object): 'conductor': 'Conductor', 'conductor_group': 'Conductor Group', 'console_enabled': 'Console Enabled', + 'count': 'Count', 'created_at': 'Created At', 'default_bios_interface': 'Default BIOS Interface', 'default_boot_interface': 'Default Boot Interface', @@ -643,3 +644,8 @@ NODE_HISTORY_DETAILED_RESOURCE = Resource( 'conductor', 'user'] ) + +SHARD_RESOURCE = Resource( + ['name', + 'count'] +) diff --git a/ironicclient/v1/shard.py b/ironicclient/v1/shard.py new file mode 100644 index 000000000..b7fbff6d9 --- /dev/null +++ b/ironicclient/v1/shard.py @@ -0,0 +1,39 @@ +# 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 ironicclient.common import base + + +class Shard(base.Resource): + def __repr__(self): + return "" % self._info + + +class ShardManager(base.Manager): + resource_class = Shard + _resource_name = 'shards' + + def list(self, os_ironic_api_version=None, global_request_id=None): + """Retrieve a list of shards. + + :param os_ironic_api_version: String version (e.g. "1.35") to use for + the request. If not specified, the client's default is used. + + :param global_request_id: String containing global request ID header + value (in form "req-") to use for the request. + + :returns: A list of conductors. + + """ + header_values = {"os_ironic_api_version": os_ironic_api_version, + "global_request_id": global_request_id} + return self._list(self._path(None), "shards", **header_values) diff --git a/setup.cfg b/setup.cfg index 82e752fd5..908a4416a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -118,6 +118,7 @@ openstack.baremetal.v1 = baremetal_port_group_set = ironicclient.osc.v1.baremetal_portgroup:SetBaremetalPortGroup baremetal_port_group_show = ironicclient.osc.v1.baremetal_portgroup:ShowBaremetalPortGroup baremetal_port_group_unset = ironicclient.osc.v1.baremetal_portgroup:UnsetBaremetalPortGroup + baremetal_shard_list = ironicclient.osc.v1.baremetal_shard:ListBaremetalShard baremetal_volume_connector_create = ironicclient.osc.v1.baremetal_volume_connector:CreateBaremetalVolumeConnector baremetal_volume_connector_delete = ironicclient.osc.v1.baremetal_volume_connector:DeleteBaremetalVolumeConnector baremetal_volume_connector_list = ironicclient.osc.v1.baremetal_volume_connector:ListBaremetalVolumeConnector