Skip to content

Commit a0f777f

Browse files
author
kyu0
committed
Network: Show ip_availability_details in ip availability
The Neutron ``network-ip-availability-details`` extension[0] adds an ``ip_availability_details`` attribute to the network-ip-availabilities resource. It reports the total and used IP counts separately for the subnet CIDRs and for the subnet allocation pools, which the existing ``total_ips`` and ``used_ips`` fields cannot express on their own: ``total_ips`` is relative to the allocation pools when a subnet has any, while ``used_ips`` is always relative to the whole subnet. Format the new attribute in ``ip availability show`` and report its four counts as columns of ``ip availability list``. The columns are left empty when the extension is not enabled. [0] https://review.opendev.org/c/openstack/neutron-lib/+/965087 Related-Bug: #2107316 Change-Id: I7aa941c768d39d25bf4007ba6dc16106c8e27eef Signed-off-by: Kyuyeong Lee <kyu0.lee@samsung.com>
1 parent 01668c8 commit a0f777f

5 files changed

Lines changed: 121 additions & 0 deletions

File tree

openstackclient/network/v2/ip_availability.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,18 @@
2828

2929
_formatters = {
3030
'subnet_ip_availability': format_columns.ListDictColumn,
31+
'ip_availability_details': format_columns.DictColumn,
3132
}
3233

34+
# Keys of the 'ip_availability_details' attribute, added by the
35+
# 'network-ip-availability-details' extension.
36+
_DETAIL_FIELDS = (
37+
'total_ips_in_subnet',
38+
'total_ips_in_allocation_pool',
39+
'used_ips_in_subnet',
40+
'used_ips_in_allocation_pool',
41+
)
42+
3343

3444
def _get_columns(
3545
item: _ip_availability.NetworkIPAvailability,
@@ -40,6 +50,18 @@ def _get_columns(
4050
)
4151

4252

53+
def _get_detail(
54+
item: _ip_availability.NetworkIPAvailability, field: str
55+
) -> Any:
56+
"""Return a single key of the 'ip_availability_details' attribute.
57+
58+
The attribute is only present when the 'network-ip-availability-details'
59+
extension is enabled, so fall back to an empty value.
60+
"""
61+
details = getattr(item, 'ip_availability_details', None) or {}
62+
return details.get(field, '')
63+
64+
4365
class ListIPAvailability(command.Lister):
4466
_description = _("List IP availability for network")
4567

@@ -85,6 +107,10 @@ def take_action(
85107
'Network Name',
86108
'Total IPs',
87109
'Used IPs',
110+
'Total IPs in Subnet',
111+
'Total IPs in Allocation Pool',
112+
'Used IPs in Subnet',
113+
'Used IPs in Allocation Pool',
88114
)
89115

90116
filters = {}
@@ -113,6 +139,7 @@ def take_action(
113139
s,
114140
columns,
115141
)
142+
+ tuple(_get_detail(s, field) for field in _DETAIL_FIELDS)
116143
for s in data
117144
),
118145
)

openstackclient/tests/functional/network/v2/test_ip_availability.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,39 @@ def test_ip_availability_show(self):
7575
self.NETWORK_NAME,
7676
cmd_output['network_name'],
7777
)
78+
79+
def test_ip_availability_details(self):
80+
"""Test the network-ip-availability-details extension fields"""
81+
if not self.is_extension_enabled('network-ip-availability-details'):
82+
self.skipTest("No network-ip-availability-details extension")
83+
84+
cmd_output = self.openstack(
85+
'ip availability show ' + self.NETWORK_NAME,
86+
parse_output=True,
87+
)
88+
details = cmd_output['ip_availability_details']
89+
# 10.10.10.0/24 minus the network and broadcast addresses
90+
self.assertEqual(254, details['total_ips_in_subnet'])
91+
self.assertGreaterEqual(
92+
details['total_ips_in_subnet'],
93+
details['total_ips_in_allocation_pool'],
94+
)
95+
self.assertGreaterEqual(
96+
details['used_ips_in_subnet'],
97+
details['used_ips_in_allocation_pool'],
98+
)
99+
100+
cmd_output = self.openstack(
101+
'ip availability list',
102+
parse_output=True,
103+
)
104+
network = next(
105+
x for x in cmd_output if x['Network Name'] == self.NETWORK_NAME
106+
)
107+
self.assertEqual(
108+
details['total_ips_in_subnet'], network['Total IPs in Subnet']
109+
)
110+
self.assertEqual(
111+
details['total_ips_in_allocation_pool'],
112+
network['Total IPs in Allocation Pool'],
113+
)

openstackclient/tests/unit/network/v2/fakes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,12 @@ def create_one_ip_availability(attrs=None):
901901
'subnet_ip_availability': [],
902902
'total_ips': 254,
903903
'used_ips': 6,
904+
'ip_availability_details': {
905+
'total_ips_in_subnet': 254,
906+
'total_ips_in_allocation_pool': 253,
907+
'used_ips_in_subnet': 6,
908+
'used_ips_in_allocation_pool': 4,
909+
},
904910
'location': 'MUNCHMUNCHMUNCH',
905911
}
906912
network_ip_attrs.update(attrs)

openstackclient/tests/unit/network/v2/test_ip_availability.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ class TestListIPAvailability(network_fakes.TestNetworkV2):
2929
'Network Name',
3030
'Total IPs',
3131
'Used IPs',
32+
'Total IPs in Subnet',
33+
'Total IPs in Allocation Pool',
34+
'Used IPs in Subnet',
35+
'Used IPs in Allocation Pool',
3236
)
3337
data = []
3438
for net in _ip_availability:
@@ -38,6 +42,10 @@ class TestListIPAvailability(network_fakes.TestNetworkV2):
3842
net.network_name,
3943
net.total_ips,
4044
net.used_ips,
45+
net.ip_availability_details['total_ips_in_subnet'],
46+
net.ip_availability_details['total_ips_in_allocation_pool'],
47+
net.ip_availability_details['used_ips_in_subnet'],
48+
net.ip_availability_details['used_ips_in_allocation_pool'],
4149
)
4250
)
4351

@@ -124,6 +132,37 @@ def test_list_project(self):
124132
self.assertEqual(self.columns, columns)
125133
self.assertCountEqual(self.data, list(data))
126134

135+
def test_list_without_ip_availability_details(self):
136+
# The 'network-ip-availability-details' extension may not be
137+
# enabled, in which case the detail columns are left empty.
138+
ip_availability = network_fakes.create_one_ip_availability(
139+
attrs={'ip_availability_details': None}
140+
)
141+
self.network_client.network_ip_availabilities.return_value = [
142+
ip_availability
143+
]
144+
145+
parsed_args = self.check_parser(self.cmd, [], [])
146+
147+
columns, data = self.cmd.take_action(parsed_args)
148+
149+
self.assertEqual(self.columns, columns)
150+
self.assertEqual(
151+
[
152+
(
153+
ip_availability.network_id,
154+
ip_availability.network_name,
155+
ip_availability.total_ips,
156+
ip_availability.used_ips,
157+
'',
158+
'',
159+
'',
160+
'',
161+
)
162+
],
163+
list(data),
164+
)
165+
127166

128167
class TestShowIPAvailability(network_fakes.TestNetworkV2):
129168
_network = network_fakes.create_one_network()
@@ -132,6 +171,7 @@ class TestShowIPAvailability(network_fakes.TestNetworkV2):
132171
)
133172

134173
columns = (
174+
'ip_availability_details',
135175
'network_id',
136176
'network_name',
137177
'project_id',
@@ -140,6 +180,7 @@ class TestShowIPAvailability(network_fakes.TestNetworkV2):
140180
'used_ips',
141181
)
142182
data = (
183+
format_columns.DictColumn(_ip_availability.ip_availability_details),
143184
_ip_availability.network_id,
144185
_ip_availability.network_name,
145186
_ip_availability.project_id,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
features:
3+
- |
4+
The ``ip availability show`` command now reports the
5+
``ip_availability_details`` attribute and the ``ip availability list``
6+
command now shows the ``Total IPs in Subnet``, ``Total IPs in Allocation
7+
Pool``, ``Used IPs in Subnet`` and ``Used IPs in Allocation Pool``
8+
columns. These report the total and used IP counts separately for the
9+
subnet CIDRs and for the subnet allocation pools, and require the
10+
``network-ip-availability-details`` extension. The new columns are empty
11+
when the extension is not enabled.

0 commit comments

Comments
 (0)