Get "security_groups" when port list

Neutron API is accepting 'security_groups' field  in
order to return the list of security_groups attached
to a port, but openstackclient is parsing the output
over a Openstack Port object that has security_group_ids
to map. This patch sends to the Neutron API the expected
field value and replace the output key to allow the
mapping just in case '--long' argument is passed.

Closes-Bug: #2095414
Change-Id: I188edc3c620ce29d7b16497ca24fd7d972a06618
This commit is contained in:
Fernando Royo 2025-01-21 14:23:07 +01:00
parent 146a1814b6
commit d2d7219231
3 changed files with 51 additions and 23 deletions
openstackclient
network/v2
tests
functional/network/v2
unit/network/v2

@ -866,35 +866,30 @@ class ListPort(command.Lister):
network_client = self.app.client_manager.network network_client = self.app.client_manager.network
identity_client = self.app.client_manager.identity identity_client = self.app.client_manager.identity
columns = ( columns = [
'id', 'id',
'name', 'name',
'mac_address', 'mac_address',
'fixed_ips', 'fixed_ips',
'status', 'status',
) ]
column_headers = ( column_headers = [
'ID', 'ID',
'Name', 'Name',
'MAC Address', 'MAC Address',
'Fixed IP Addresses', 'Fixed IP Addresses',
'Status', 'Status',
) ]
filters = {} filters = {}
if parsed_args.long: if parsed_args.long:
columns += ( columns.extend(
'security_group_ids', ['security_groups', 'device_owner', 'tags', 'trunk_details']
'device_owner',
'tags',
'trunk_details',
) )
column_headers += ( column_headers.extend(
'Security Groups', ['Security Groups', 'Device Owner', 'Tags', 'Trunk subports']
'Device Owner',
'Tags',
'Trunk subports',
) )
if parsed_args.device_owner is not None: if parsed_args.device_owner is not None:
filters['device_owner'] = parsed_args.device_owner filters['device_owner'] = parsed_args.device_owner
if parsed_args.device_id is not None: if parsed_args.device_id is not None:
@ -942,6 +937,12 @@ class ListPort(command.Lister):
data = network_client.ports(fields=columns, **filters) data = network_client.ports(fields=columns, **filters)
if parsed_args.long:
columns = [
'security_group_ids' if item == 'security_groups' else item
for item in columns
]
headers, attrs = utils.calculate_header_and_attrs( headers, attrs = utils.calculate_header_and_attrs(
column_headers, columns, parsed_args column_headers, columns, parsed_args
) )

@ -81,10 +81,20 @@ class PortTests(common.NetworkTagTests):
self.addCleanup(self.openstack, f'port delete {id1}') self.addCleanup(self.openstack, f'port delete {id1}')
self.assertEqual(self.NAME, json_output.get('name')) self.assertEqual(self.NAME, json_output.get('name'))
# sg for port2
sg_name1 = uuid.uuid4().hex
json_output = self.openstack( json_output = self.openstack(
f'port create --network {self.NETWORK_NAME} {self.NAME}x', f'security group create {sg_name1}',
parse_output=True, parse_output=True,
) )
sg_id1 = json_output.get('id')
self.addCleanup(self.openstack, f'security group delete {sg_id1}')
json_output = self.openstack(
f'port create --network {self.NETWORK_NAME} '
f'--security-group {sg_name1} {self.NAME}x',
parse_output=True,
)
id2 = json_output.get('id') id2 = json_output.get('id')
self.assertIsNotNone(id2) self.assertIsNotNone(id2)
mac2 = json_output.get('mac_address') mac2 = json_output.get('mac_address')
@ -113,6 +123,12 @@ class PortTests(common.NetworkTagTests):
id_list = [item.get('ID') for item in json_output] id_list = [item.get('ID') for item in json_output]
self.assertIn(id1, id_list) self.assertIn(id1, id_list)
self.assertIn(id2, id_list) self.assertIn(id2, id_list)
item_sg_map = {
item.get('ID'): item.get('Security Groups') for item in json_output
}
self.assertIn(id1, item_sg_map.keys())
self.assertIn(id2, item_sg_map.keys())
self.assertIn([sg_id1], item_sg_map.values())
# Test list --mac-address # Test list --mac-address
json_output = self.openstack( json_output = self.openstack(
@ -127,6 +143,17 @@ class PortTests(common.NetworkTagTests):
self.assertNotIn(mac1, item_map.values()) self.assertNotIn(mac1, item_map.values())
self.assertIn(mac2, item_map.values()) self.assertIn(mac2, item_map.values())
# Test list --security-group
json_output = self.openstack(
f'port list --security-group {sg_id1}',
parse_output=True,
)
item_map = {
item.get('ID'): item.get('Security Groups') for item in json_output
}
self.assertNotIn(id1, item_map.keys())
self.assertIn(id2, item_map.keys())
# Test list with unknown fields # Test list with unknown fields
json_output = self.openstack( json_output = self.openstack(
'port list -c ID -c Name -c device_id', 'port list -c ID -c Name -c device_id',

@ -24,13 +24,13 @@ from openstackclient.tests.unit.network.v2 import fakes as network_fakes
from openstackclient.tests.unit import utils as test_utils from openstackclient.tests.unit import utils as test_utils
LIST_FIELDS_TO_RETRIEVE = ('id', 'name', 'mac_address', 'fixed_ips', 'status') LIST_FIELDS_TO_RETRIEVE = ['id', 'name', 'mac_address', 'fixed_ips', 'status']
LIST_FIELDS_TO_RETRIEVE_LONG = ( LIST_FIELDS_TO_RETRIEVE_LONG = [
'security_group_ids', 'security_groups',
'device_owner', 'device_owner',
'tags', 'tags',
'trunk_details', 'trunk_details',
) ]
class TestPort(network_fakes.TestNetworkV2): class TestPort(network_fakes.TestNetworkV2):
@ -1274,15 +1274,15 @@ class TestListPort(compute_fakes.FakeClientMixin, TestPort):
) )
_ports = (_pport, _sport1, _sport2) _ports = (_pport, _sport1, _sport2)
columns = ( columns = [
'ID', 'ID',
'Name', 'Name',
'MAC Address', 'MAC Address',
'Fixed IP Addresses', 'Fixed IP Addresses',
'Status', 'Status',
) ]
columns_long = ( columns_long = [
'ID', 'ID',
'Name', 'Name',
'MAC Address', 'MAC Address',
@ -1292,7 +1292,7 @@ class TestListPort(compute_fakes.FakeClientMixin, TestPort):
'Device Owner', 'Device Owner',
'Tags', 'Tags',
'Trunk subports', 'Trunk subports',
) ]
data = [] data = []
for prt in _ports: for prt in _ports: