Change approach to detailed listings of baremetal resources

The current approach with separate ResourceDetail classes suffers
from serious flows and is barely usable. For example for nodes:
* get_node() returns complete Node objects
* nodes() returns incomplete Node objects
* nodes(details=True) returns NodeDetail objects, which are exactly
  like complete Node objects, except that you cannot do anything
  with them: updating, listing VIFs, etc.
This is confusing and essentially requires the caller to issue
a redundant get_node call after listing detailed nodes.

This change consolidates everything into Node (Port, etc) objects
and deprecates NodeDetail (PortDetail, etc). I would highly recommend
the same approach taken for other ResourceDetail objects.

Change-Id: Idedee2b394e2ed412a69880d4bc9315737e1c3ed
This commit is contained in:
Dmitry Tantsur 2018-11-22 15:00:45 +01:00
parent cdef4806ce
commit 5c5478d774
16 changed files with 145 additions and 247 deletions

View File

@ -55,3 +55,32 @@ STATE_VERSIONS = {
VIF_VERSION = '1.28' VIF_VERSION = '1.28'
"""API version in which the VIF operations were introduced.""" """API version in which the VIF operations were introduced."""
class ListMixin(object):
@classmethod
def list(cls, session, details=False, **params):
"""This method is a generator which yields resource objects.
This resource object list generator handles pagination and takes query
params for response filtering.
:param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter`
:param bool details: Whether to return detailed node records
:param dict params: These keyword arguments are passed through the
:meth:`~openstack.resource.QueryParameter._transpose` method
to find if any of them match expected query parameters to be
sent in the *params* argument to
:meth:`~keystoneauth1.adapter.Adapter.get`.
:return: A generator of :class:`openstack.resource.Resource` objects.
:raises: :exc:`~openstack.exceptions.InvalidResourceQuery` if query
contains invalid params.
"""
base_path = cls.base_path
if details:
base_path += '/detail'
return super(ListMixin, cls).list(session, paginated=True,
base_path=base_path, **params)

View File

@ -61,8 +61,7 @@ class Proxy(proxy.Proxy):
:returns: A generator of chassis instances. :returns: A generator of chassis instances.
""" """
cls = _chassis.ChassisDetail if details else _chassis.Chassis return _chassis.Chassis.list(self, details=details, **query)
return self._list(cls, paginated=True, **query)
def create_chassis(self, **attrs): def create_chassis(self, **attrs):
"""Create a new chassis from attributes. """Create a new chassis from attributes.
@ -198,10 +197,9 @@ class Proxy(proxy.Proxy):
direction of the server attribute that is provided as the direction of the server attribute that is provided as the
``sort_key``. ``sort_key``.
:returns: A generator of node instances. :returns: A generator of :class:`~openstack.baremetal.v1.node.Node`
""" """
cls = _node.NodeDetail if details else _node.Node return _node.Node.list(self, details=details, **query)
return self._list(cls, paginated=True, **query)
def create_node(self, **attrs): def create_node(self, **attrs):
"""Create a new node from attributes. """Create a new node from attributes.
@ -442,8 +440,7 @@ class Proxy(proxy.Proxy):
:returns: A generator of port instances. :returns: A generator of port instances.
""" """
cls = _port.PortDetail if details else _port.Port return _port.Port.list(self, details=details, **query)
return self._list(cls, paginated=True, **query)
def create_port(self, **attrs): def create_port(self, **attrs):
"""Create a new port from attributes. """Create a new port from attributes.
@ -555,8 +552,7 @@ class Proxy(proxy.Proxy):
:returns: A generator of port group instances. :returns: A generator of port group instances.
""" """
cls = _portgroup.PortGroupDetail if details else _portgroup.PortGroup return _portgroup.PortGroup.list(self, details=details, **query)
return self._list(cls, paginated=True, **query)
def create_port_group(self, **attrs): def create_port_group(self, **attrs):
"""Create a new portgroup from attributes. """Create a new portgroup from attributes.

View File

@ -10,10 +10,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from openstack.baremetal.v1 import _common
from openstack import resource from openstack import resource
class Chassis(resource.Resource): class Chassis(_common.ListMixin, resource.Resource):
resources_key = 'chassis' resources_key = 'chassis'
base_path = '/chassis' base_path = '/chassis'
@ -47,16 +48,4 @@ class Chassis(resource.Resource):
updated_at = resource.Body('updated_at') updated_at = resource.Body('updated_at')
class ChassisDetail(Chassis): ChassisDetail = Chassis
base_path = '/chassis/detail'
# capabilities
allow_create = False
allow_fetch = False
allow_commit = False
allow_delete = False
allow_list = True
#: The UUID for the chassis
id = resource.Body('uuid', alternate_id=True)

View File

@ -34,7 +34,7 @@ class ValidationResult(object):
self.reason = reason self.reason = reason
class Node(resource.Resource): class Node(_common.ListMixin, resource.Resource):
resources_key = 'nodes' resources_key = 'nodes'
base_path = '/nodes' base_path = '/nodes'
@ -589,23 +589,4 @@ class Node(resource.Resource):
for key, value in result.items()} for key, value in result.items()}
class NodeDetail(Node): NodeDetail = Node
base_path = '/nodes/detail'
# capabilities
allow_create = False
allow_fetch = False
allow_commit = False
allow_delete = False
allow_list = True
_query_mapping = resource.QueryParameters(
'associated', 'conductor_group', 'driver', 'fault',
'provision_state', 'resource_class',
instance_id='instance_uuid',
is_maintenance='maintenance',
)
#: The UUID of the node resource.
id = resource.Body("uuid", alternate_id=True)

View File

@ -10,10 +10,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from openstack.baremetal.v1 import _common
from openstack import resource from openstack import resource
class Port(resource.Resource): class Port(_common.ListMixin, resource.Resource):
resources_key = 'ports' resources_key = 'ports'
base_path = '/ports' base_path = '/ports'
@ -29,6 +30,7 @@ class Port(resource.Resource):
_query_mapping = resource.QueryParameters( _query_mapping = resource.QueryParameters(
'address', 'fields', 'node', 'portgroup', 'address', 'fields', 'node', 'portgroup',
node_id='node_uuid',
) )
# The physical_network field introduced in 1.34 # The physical_network field introduced in 1.34
@ -67,21 +69,4 @@ class Port(resource.Resource):
updated_at = resource.Body('updated_at') updated_at = resource.Body('updated_at')
class PortDetail(Port): PortDetail = Port
base_path = '/ports/detail'
# capabilities
allow_create = False
allow_fetch = False
allow_commit = False
allow_delete = False
allow_list = True
_query_mapping = resource.QueryParameters(
'address', 'fields', 'node', 'portgroup',
node_id='node_uuid',
)
#: The UUID of the port
id = resource.Body('uuid', alternate_id=True)

View File

@ -10,10 +10,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from openstack.baremetal.v1 import _common
from openstack import resource from openstack import resource
class PortGroup(resource.Resource): class PortGroup(_common.ListMixin, resource.Resource):
resources_key = 'portgroups' resources_key = 'portgroups'
base_path = '/portgroups' base_path = '/portgroups'
@ -66,19 +67,4 @@ class PortGroup(resource.Resource):
updated_at = resource.Body('updated_at') updated_at = resource.Body('updated_at')
class PortGroupDetail(PortGroup): PortGroupDetail = PortGroup
base_path = '/portgroups/detail'
allow_create = False
allow_fetch = False
allow_commit = False
allow_delete = False
allow_list = True
_query_mapping = resource.QueryParameters(
'node', 'address',
)
#: The UUID for the portgroup
id = resource.Body('uuid', alternate_id=True)

View File

@ -1206,7 +1206,7 @@ class Resource(dict):
return self return self
@classmethod @classmethod
def list(cls, session, paginated=False, **params): def list(cls, session, paginated=False, base_path=None, **params):
"""This method is a generator which yields resource objects. """This method is a generator which yields resource objects.
This resource object list generator handles pagination and takes query This resource object list generator handles pagination and takes query
@ -1220,6 +1220,9 @@ class Resource(dict):
**When paginated is False only one **When paginated is False only one
page of data will be returned regardless page of data will be returned regardless
of the API's support of pagination.** of the API's support of pagination.**
:param str base_path: Base part of the URI for listing resources, if
different from
:data:`~openstack.resource.Resource.base_path`.
:param dict params: These keyword arguments are passed through the :param dict params: These keyword arguments are passed through the
:meth:`~openstack.resource.QueryParamter._transpose` method :meth:`~openstack.resource.QueryParamter._transpose` method
to find if any of them match expected query parameters to be to find if any of them match expected query parameters to be
@ -1241,9 +1244,11 @@ class Resource(dict):
session = cls._get_session(session) session = cls._get_session(session)
microversion = cls._get_microversion_for_list(session) microversion = cls._get_microversion_for_list(session)
cls._query_mapping._validate(params, base_path=cls.base_path) if base_path is None:
base_path = cls.base_path
cls._query_mapping._validate(params, base_path=base_path)
query_params = cls._query_mapping._transpose(params) query_params = cls._query_mapping._transpose(params)
uri = cls.base_path % params uri = base_path % params
limit = query_params.get('limit') limit = query_params.get('limit')

View File

@ -86,6 +86,20 @@ class TestBareMetalNode(base.BaseBaremetalTest):
node = self.conn.baremetal.get_node('node-name') node = self.conn.baremetal.get_node('node-name')
self.assertIsNone(node.instance_id) self.assertIsNone(node.instance_id)
def test_node_list_update_delete(self):
self.create_node(name='node-name', extra={'foo': 'bar'})
node = next(n for n in
self.conn.baremetal.nodes(details=True,
provision_state='available',
is_maintenance=False,
associated=False)
if n.name == 'node-name')
self.assertEqual(node.extra, {'foo': 'bar'})
# This test checks that resources returned from listing are usable
self.conn.baremetal.update_node(node, extra={'foo': 42})
self.conn.baremetal.delete_node(node, ignore_missing=False)
def test_node_create_in_enroll_provide(self): def test_node_create_in_enroll_provide(self):
node = self.create_node(provision_state='enroll') node = self.create_node(provision_state='enroll')
self.node_id = node.id self.node_id = node.id

View File

@ -53,6 +53,17 @@ class TestBareMetalPort(base.BaseBaremetalTest):
ports = self.conn.baremetal.ports(node='test-node') ports = self.conn.baremetal.ports(node='test-node')
self.assertEqual([p.id for p in ports], [port1.id]) self.assertEqual([p.id for p in ports], [port1.id])
def test_port_list_update_delete(self):
self.create_port(address='11:22:33:44:55:66', node_id=self.node.id,
extra={'foo': 'bar'})
port = next(self.conn.baremetal.ports(details=True,
address='11:22:33:44:55:66'))
self.assertEqual(port.extra, {'foo': 'bar'})
# This test checks that resources returned from listing are usable
self.conn.baremetal.update_port(port, extra={'foo': 42})
self.conn.baremetal.delete_port(port, ignore_missing=False)
def test_port_update(self): def test_port_update(self):
port = self.create_port(address='11:22:33:44:55:66') port = self.create_port(address='11:22:33:44:55:66')
port.address = '66:55:44:33:22:11' port.address = '66:55:44:33:22:11'

View File

@ -51,6 +51,17 @@ class TestBareMetalPortGroup(base.BaseBaremetalTest):
pgs = self.conn.baremetal.port_groups(node='test-node') pgs = self.conn.baremetal.port_groups(node='test-node')
self.assertEqual([p.id for p in pgs], [pg1.id]) self.assertEqual([p.id for p in pgs], [pg1.id])
def test_port_list_update_delete(self):
self.create_port_group(address='11:22:33:44:55:66',
extra={'foo': 'bar'})
port_group = next(self.conn.baremetal.port_groups(
details=True, address='11:22:33:44:55:66'))
self.assertEqual(port_group.extra, {'foo': 'bar'})
# This test checks that resources returned from listing are usable
self.conn.baremetal.update_port_group(port_group, extra={'foo': 42})
self.conn.baremetal.delete_port_group(port_group, ignore_missing=False)
def test_port_group_update(self): def test_port_group_update(self):
port_group = self.create_port_group() port_group = self.create_port_group()
port_group.extra = {'answer': 42} port_group.extra = {'answer': 42}

View File

@ -66,27 +66,3 @@ class TestChassis(base.TestCase):
self.assertEqual(FAKE['links'], sot.links) self.assertEqual(FAKE['links'], sot.links)
self.assertEqual(FAKE['nodes'], sot.nodes) self.assertEqual(FAKE['nodes'], sot.nodes)
self.assertEqual(FAKE['updated_at'], sot.updated_at) self.assertEqual(FAKE['updated_at'], sot.updated_at)
class TestChassisDetail(base.TestCase):
def test_basic(self):
sot = chassis.ChassisDetail()
self.assertIsNone(sot.resource_key)
self.assertEqual('chassis', sot.resources_key)
self.assertEqual('/chassis/detail', sot.base_path)
self.assertFalse(sot.allow_create)
self.assertFalse(sot.allow_fetch)
self.assertFalse(sot.allow_commit)
self.assertFalse(sot.allow_delete)
self.assertTrue(sot.allow_list)
def test_instantiate(self):
sot = chassis.ChassisDetail(**FAKE)
self.assertEqual(FAKE['uuid'], sot.id)
self.assertEqual(FAKE['created_at'], sot.created_at)
self.assertEqual(FAKE['description'], sot.description)
self.assertEqual(FAKE['extra'], sot.extra)
self.assertEqual(FAKE['links'], sot.links)
self.assertEqual(FAKE['nodes'], sot.nodes)
self.assertEqual(FAKE['updated_at'], sot.updated_at)

View File

@ -153,58 +153,6 @@ class TestNode(base.TestCase):
self.assertEqual('available', sot.provision_state) self.assertEqual('available', sot.provision_state)
class TestNodeDetail(base.TestCase):
def test_basic(self):
sot = node.NodeDetail()
self.assertIsNone(sot.resource_key)
self.assertEqual('nodes', sot.resources_key)
self.assertEqual('/nodes/detail', sot.base_path)
self.assertFalse(sot.allow_create)
self.assertFalse(sot.allow_fetch)
self.assertFalse(sot.allow_commit)
self.assertFalse(sot.allow_delete)
self.assertTrue(sot.allow_list)
def test_instantiate(self):
sot = node.NodeDetail(**FAKE)
self.assertEqual(FAKE['uuid'], sot.id)
self.assertEqual(FAKE['name'], sot.name)
self.assertEqual(FAKE['chassis_uuid'], sot.chassis_id)
self.assertEqual(FAKE['clean_step'], sot.clean_step)
self.assertEqual(FAKE['created_at'], sot.created_at)
self.assertEqual(FAKE['driver'], sot.driver)
self.assertEqual(FAKE['driver_info'], sot.driver_info)
self.assertEqual(FAKE['driver_internal_info'],
sot.driver_internal_info)
self.assertEqual(FAKE['extra'], sot.extra)
self.assertEqual(FAKE['instance_info'], sot.instance_info)
self.assertEqual(FAKE['instance_uuid'], sot.instance_id)
self.assertEqual(FAKE['console_enabled'], sot.is_console_enabled)
self.assertEqual(FAKE['maintenance'], sot.is_maintenance)
self.assertEqual(FAKE['last_error'], sot.last_error)
self.assertEqual(FAKE['links'], sot.links)
self.assertEqual(FAKE['maintenance_reason'], sot.maintenance_reason)
self.assertEqual(FAKE['name'], sot.name)
self.assertEqual(FAKE['network_interface'], sot.network_interface)
self.assertEqual(FAKE['ports'], sot.ports)
self.assertEqual(FAKE['portgroups'], sot.port_groups)
self.assertEqual(FAKE['power_state'], sot.power_state)
self.assertEqual(FAKE['properties'], sot.properties)
self.assertEqual(FAKE['provision_state'], sot.provision_state)
self.assertEqual(FAKE['raid_config'], sot.raid_config)
self.assertEqual(FAKE['reservation'], sot.reservation)
self.assertEqual(FAKE['resource_class'], sot.resource_class)
self.assertEqual(FAKE['states'], sot.states)
self.assertEqual(FAKE['target_provision_state'],
sot.target_provision_state)
self.assertEqual(FAKE['target_power_state'], sot.target_power_state)
self.assertEqual(FAKE['target_raid_config'], sot.target_raid_config)
self.assertEqual(FAKE['updated_at'], sot.updated_at)
@mock.patch('time.sleep', lambda _t: None) @mock.patch('time.sleep', lambda _t: None)
@mock.patch.object(node.Node, 'fetch', autospec=True) @mock.patch.object(node.Node, 'fetch', autospec=True)
class TestNodeWaitForProvisionState(base.TestCase): class TestNodeWaitForProvisionState(base.TestCase):

View File

@ -70,32 +70,3 @@ class TestPort(base.TestCase):
self.assertEqual(FAKE['portgroup_uuid'], sot.port_group_id) self.assertEqual(FAKE['portgroup_uuid'], sot.port_group_id)
self.assertEqual(FAKE['pxe_enabled'], sot.is_pxe_enabled) self.assertEqual(FAKE['pxe_enabled'], sot.is_pxe_enabled)
self.assertEqual(FAKE['updated_at'], sot.updated_at) self.assertEqual(FAKE['updated_at'], sot.updated_at)
class TestPortDetail(base.TestCase):
def test_basic(self):
sot = port.PortDetail()
self.assertIsNone(sot.resource_key)
self.assertEqual('ports', sot.resources_key)
self.assertEqual('/ports/detail', sot.base_path)
self.assertFalse(sot.allow_create)
self.assertFalse(sot.allow_fetch)
self.assertFalse(sot.allow_commit)
self.assertFalse(sot.allow_delete)
self.assertTrue(sot.allow_list)
def test_instantiate(self):
sot = port.PortDetail(**FAKE)
self.assertEqual(FAKE['uuid'], sot.id)
self.assertEqual(FAKE['address'], sot.address)
self.assertEqual(FAKE['created_at'], sot.created_at)
self.assertEqual(FAKE['extra'], sot.extra)
self.assertEqual(FAKE['internal_info'], sot.internal_info)
self.assertEqual(FAKE['links'], sot.links)
self.assertEqual(FAKE['local_link_connection'],
sot.local_link_connection)
self.assertEqual(FAKE['node_uuid'], sot.node_id)
self.assertEqual(FAKE['portgroup_uuid'], sot.port_group_id)
self.assertEqual(FAKE['pxe_enabled'], sot.is_pxe_enabled)
self.assertEqual(FAKE['updated_at'], sot.updated_at)

View File

@ -75,32 +75,3 @@ class TestPortGroup(base.TestCase):
self.assertEqual(FAKE['standalone_ports_supported'], self.assertEqual(FAKE['standalone_ports_supported'],
sot.is_standalone_ports_supported) sot.is_standalone_ports_supported)
self.assertEqual(FAKE['updated_at'], sot.updated_at) self.assertEqual(FAKE['updated_at'], sot.updated_at)
class TestPortGroupDetail(base.TestCase):
def test_basic(self):
sot = port_group.PortGroupDetail()
self.assertIsNone(sot.resource_key)
self.assertEqual('portgroups', sot.resources_key)
self.assertEqual('/portgroups/detail', sot.base_path)
self.assertFalse(sot.allow_create)
self.assertFalse(sot.allow_fetch)
self.assertFalse(sot.allow_commit)
self.assertFalse(sot.allow_delete)
self.assertTrue(sot.allow_list)
def test_instantiate(self):
sot = port_group.PortGroupDetail(**FAKE)
self.assertEqual(FAKE['uuid'], sot.id)
self.assertEqual(FAKE['address'], sot.address)
self.assertEqual(FAKE['created_at'], sot.created_at)
self.assertEqual(FAKE['extra'], sot.extra)
self.assertEqual(FAKE['internal_info'], sot.internal_info)
self.assertEqual(FAKE['links'], sot.links)
self.assertEqual(FAKE['name'], sot.name)
self.assertEqual(FAKE['node_uuid'], sot.node_id)
self.assertEqual(FAKE['ports'], sot.ports)
self.assertEqual(FAKE['standalone_ports_supported'],
sot.is_standalone_ports_supported)
self.assertEqual(FAKE['updated_at'], sot.updated_at)

View File

@ -17,6 +17,7 @@ from openstack.baremetal.v1 import chassis
from openstack.baremetal.v1 import driver from openstack.baremetal.v1 import driver
from openstack.baremetal.v1 import node from openstack.baremetal.v1 import node
from openstack.baremetal.v1 import port from openstack.baremetal.v1 import port
from openstack.baremetal.v1 import port_group
from openstack import exceptions from openstack import exceptions
from openstack.tests.unit import base from openstack.tests.unit import base
from openstack.tests.unit import test_proxy_base from openstack.tests.unit import test_proxy_base
@ -34,17 +35,17 @@ class TestBaremetalProxy(test_proxy_base.TestProxyBase):
def test_get_driver(self): def test_get_driver(self):
self.verify_get(self.proxy.get_driver, driver.Driver) self.verify_get(self.proxy.get_driver, driver.Driver)
def test_chassis_detailed(self): @mock.patch.object(chassis.Chassis, 'list')
self.verify_list(self.proxy.chassis, chassis.ChassisDetail, def test_chassis_detailed(self, mock_list):
paginated=True, result = self.proxy.chassis(details=True, query=1)
method_kwargs={"details": True, "query": 1}, self.assertIs(result, mock_list.return_value)
expected_kwargs={"query": 1}) mock_list.assert_called_once_with(self.proxy, details=True, query=1)
def test_chassis_not_detailed(self): @mock.patch.object(chassis.Chassis, 'list')
self.verify_list(self.proxy.chassis, chassis.Chassis, def test_chassis_not_detailed(self, mock_list):
paginated=True, result = self.proxy.chassis(query=1)
method_kwargs={"details": False, "query": 1}, self.assertIs(result, mock_list.return_value)
expected_kwargs={"query": 1}) mock_list.assert_called_once_with(self.proxy, details=False, query=1)
def test_create_chassis(self): def test_create_chassis(self):
self.verify_create(self.proxy.create_chassis, chassis.Chassis) self.verify_create(self.proxy.create_chassis, chassis.Chassis)
@ -64,17 +65,17 @@ class TestBaremetalProxy(test_proxy_base.TestProxyBase):
def test_delete_chassis_ignore(self): def test_delete_chassis_ignore(self):
self.verify_delete(self.proxy.delete_chassis, chassis.Chassis, True) self.verify_delete(self.proxy.delete_chassis, chassis.Chassis, True)
def test_nodes_detailed(self): @mock.patch.object(node.Node, 'list')
self.verify_list(self.proxy.nodes, node.NodeDetail, def test_nodes_detailed(self, mock_list):
paginated=True, result = self.proxy.nodes(details=True, query=1)
method_kwargs={"details": True, "query": 1}, self.assertIs(result, mock_list.return_value)
expected_kwargs={"query": 1}) mock_list.assert_called_once_with(self.proxy, details=True, query=1)
def test_nodes_not_detailed(self): @mock.patch.object(node.Node, 'list')
self.verify_list(self.proxy.nodes, node.Node, def test_nodes_not_detailed(self, mock_list):
paginated=True, result = self.proxy.nodes(query=1)
method_kwargs={"details": False, "query": 1}, self.assertIs(result, mock_list.return_value)
expected_kwargs={"query": 1}) mock_list.assert_called_once_with(self.proxy, details=False, query=1)
def test_create_node(self): def test_create_node(self):
self.verify_create(self.proxy.create_node, node.Node) self.verify_create(self.proxy.create_node, node.Node)
@ -106,17 +107,17 @@ class TestBaremetalProxy(test_proxy_base.TestProxyBase):
def test_delete_node_ignore(self): def test_delete_node_ignore(self):
self.verify_delete(self.proxy.delete_node, node.Node, True) self.verify_delete(self.proxy.delete_node, node.Node, True)
def test_ports_detailed(self): @mock.patch.object(port.Port, 'list')
self.verify_list(self.proxy.ports, port.PortDetail, def test_ports_detailed(self, mock_list):
paginated=True, result = self.proxy.ports(details=True, query=1)
method_kwargs={"details": True, "query": 1}, self.assertIs(result, mock_list.return_value)
expected_kwargs={"query": 1}) mock_list.assert_called_once_with(self.proxy, details=True, query=1)
def test_ports_not_detailed(self): @mock.patch.object(port.Port, 'list')
self.verify_list(self.proxy.ports, port.Port, def test_ports_not_detailed(self, mock_list):
paginated=True, result = self.proxy.ports(query=1)
method_kwargs={"details": False, "query": 1}, self.assertIs(result, mock_list.return_value)
expected_kwargs={"query": 1}) mock_list.assert_called_once_with(self.proxy, details=False, query=1)
def test_create_port(self): def test_create_port(self):
self.verify_create(self.proxy.create_port, port.Port) self.verify_create(self.proxy.create_port, port.Port)
@ -136,6 +137,18 @@ class TestBaremetalProxy(test_proxy_base.TestProxyBase):
def test_delete_port_ignore(self): def test_delete_port_ignore(self):
self.verify_delete(self.proxy.delete_port, port.Port, True) self.verify_delete(self.proxy.delete_port, port.Port, True)
@mock.patch.object(port_group.PortGroup, 'list')
def test_port_groups_detailed(self, mock_list):
result = self.proxy.port_groups(details=True, query=1)
self.assertIs(result, mock_list.return_value)
mock_list.assert_called_once_with(self.proxy, details=True, query=1)
@mock.patch.object(port_group.PortGroup, 'list')
def test_port_groups_not_detailed(self, mock_list):
result = self.proxy.port_groups(query=1)
self.assertIs(result, mock_list.return_value)
mock_list.assert_called_once_with(self.proxy, details=False, query=1)
@mock.patch('time.sleep', lambda _sec: None) @mock.patch('time.sleep', lambda _sec: None)
@mock.patch.object(_proxy.Proxy, 'get_node', autospec=True) @mock.patch.object(_proxy.Proxy, 'get_node', autospec=True)

View File

@ -0,0 +1,12 @@
---
features:
- |
The objects returned by baremetal detailed listing functions
(``connection.baremetal.{nodes,ports,chassis,port_groups}``) are now
fully functional, e.g. can be directly updated or deleted.
deprecations:
- |
The following baremetal resource classes are no longer used and will be
removed in a future release: ``NodeDetail``, ``PortDetail``,
``ChassisDetail`` and ``PortGroupDetail``. The regular ``Node``, ``Port``,
``Chassis`` and ``PortGroup`` are now used instead.