Support /v1/shards

Unit Tests Boilerplate was generated by GPT-4o

Generated-By: GPT-4o
Change-Id: I6f45cc678a5931e3525fb85256c1608e75fe089f
This commit is contained in:
Sharpz7 2024-11-01 21:12:27 +00:00 committed by Jay Faulkner
parent 2c189bc457
commit f2bed8f253
8 changed files with 264 additions and 0 deletions

View File

@ -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))

View File

@ -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',

View File

@ -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))

View File

@ -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))

View File

@ -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):

View File

@ -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']
)

39
ironicclient/v1/shard.py Normal file
View File

@ -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 "<Shard %s>" % 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-<UUID>") 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)

View File

@ -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