Skip to content

Commit ca8ccd8

Browse files
committed
feat(network): add clear state arg to methods (#30)
1 parent 87fbf5f commit ca8ccd8

File tree

2 files changed

+77
-39
lines changed

2 files changed

+77
-39
lines changed

src/openstack_mcp_server/tools/network_tools.py

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99
)
1010

1111

12-
_UNSET = object()
13-
14-
1512
class NetworkTools:
1613
"""
1714
A class to encapsulate Network-related tools and utilities.
@@ -188,7 +185,6 @@ def _convert_to_network_model(self, openstack_network) -> Network:
188185
Convert an OpenStack network object to a Network pydantic model.
189186
190187
:param openstack_network: OpenStack network object
191-
:type openstack_network: Any
192188
:return: Pydantic Network model
193189
"""
194190
return Network(
@@ -219,11 +215,24 @@ def get_subnets(
219215
"""
220216
Get the list of Subnets with optional filtering.
221217
218+
Use this to narrow results by network, project, IP version, gateway presence, and
219+
DHCP-enabled state.
220+
221+
Notes:
222+
- has_gateway is applied client-side after retrieval and checks whether `gateway_ip` is set.
223+
- `is_dhcp_enabled` maps to Neutron's `enable_dhcp` filter.
224+
- Combining filters further restricts the result (logical AND).
225+
226+
Examples:
227+
- All IPv4 subnets in a network: `network_id="net-1"`, `ip_version=4`
228+
- Only subnets with a gateway: `has_gateway=True`
229+
- DHCP-enabled subnets for a project: `project_id="proj-1"`, `is_dhcp_enabled=True`
230+
222231
:param network_id: Filter by network ID
223232
:param ip_version: Filter by IP version (e.g., 4, 6)
224233
:param project_id: Filter by project ID
225-
:param has_gateway: Filter by whether a gateway is set
226-
:param is_dhcp_enabled: Filter by DHCP enabled state
234+
:param has_gateway: True for subnets with a gateway, False for no gateway
235+
:param is_dhcp_enabled: True for DHCP-enabled subnets, False for disabled
227236
:return: List of Subnet objects
228237
"""
229238
conn = get_openstack_conn()
@@ -309,7 +318,8 @@ def update_subnet(
309318
subnet_id: str,
310319
name: str | None = None,
311320
description: str | None = None,
312-
gateway_ip: str | None | object = _UNSET,
321+
gateway_ip: str | None = None,
322+
clear_gateway: bool = False,
313323
is_dhcp_enabled: bool | None = None,
314324
dns_nameservers: list[str] | None = None,
315325
allocation_pools: list[dict] | None = None,
@@ -320,24 +330,32 @@ def update_subnet(
320330
parameters remain untouched.
321331
322332
Typical use-cases:
323-
- Set gateway: pass gateway_ip="10.0.0.1".
324-
- Clear gateway: pass gateway_ip=None.
325-
- Enable/disable DHCP: pass is_dhcp_enabled=True or False.
326-
- Batch updates: change name/description and DNS nameservers together.
333+
- Set gateway: `gateway_ip="10.0.0.1"`.
334+
- Clear gateway: `clear_gateway=True`.
335+
- Enable/disable DHCP: `is_dhcp_enabled=True or False`.
336+
- Batch updates: update name/description and DNS nameservers together.
327337
328338
Notes:
329-
- OpenStack Neutron supports partial updates. Passing None for gateway_ip clears the gateway.
330-
- To emulate a DHCP toggle, read current state and invert it, then call this method with
331-
is_dhcp_enabled set accordingly.
339+
- `clear_gateway=True` explicitly clears `gateway_ip` (sets to None). If both `gateway_ip`
340+
and `clear_gateway=True` are provided, `clear_gateway` takes precedence.
341+
- For list-typed fields (`dns_nameservers`, `allocation_pools`, `host_routes`), the provided
342+
list replaces the entire list on the server. Pass `[]` to remove all entries.
343+
- For a DHCP toggle, read the current value via `get_subnet_detail()` and pass the inverted
344+
boolean to `is_dhcp_enabled`.
345+
346+
Examples:
347+
- Clear the gateway and disable DHCP: `clear_gateway=True`, `is_dhcp_enabled=False`
348+
- Replace DNS servers: `dns_nameservers=["8.8.8.8", "1.1.1.1"]`
332349
333350
:param subnet_id: ID of the subnet to update
334351
:param name: New subnet name
335352
:param description: New subnet description
336353
:param gateway_ip: New gateway IP
354+
:param clear_gateway: If True, clear the gateway IP (sets to None)
337355
:param is_dhcp_enabled: DHCP enabled state
338-
:param dns_nameservers: DNS nameserver list
339-
:param allocation_pools: Allocation pool list
340-
:param host_routes: Static host routes
356+
:param dns_nameservers: DNS nameserver list (replaces entire list)
357+
:param allocation_pools: Allocation pool list (replaces entire list)
358+
:param host_routes: Static host routes (replaces entire list)
341359
:return: Updated Subnet object
342360
"""
343361
conn = get_openstack_conn()
@@ -346,7 +364,9 @@ def update_subnet(
346364
update_args["name"] = name
347365
if description is not None:
348366
update_args["description"] = description
349-
if gateway_ip is not _UNSET:
367+
if clear_gateway:
368+
update_args["gateway_ip"] = None
369+
elif gateway_ip is not None:
350370
update_args["gateway_ip"] = gateway_ip
351371
if is_dhcp_enabled is not None:
352372
update_args["enable_dhcp"] = is_dhcp_enabled
@@ -531,7 +551,7 @@ def update_port(
531551
532552
Typical use-cases:
533553
- Set admin state down: is_admin_state_up=False
534-
- Toggle admin state: read current via get_port_detail() then pass the inverted value
554+
- Toggle admin state: read current via get_port_detail(); pass inverted value
535555
- Replace security groups: security_group_ids=["sg-1", "sg-2"]
536556
- Replace allowed address pairs:
537557
1) current = get_port_allowed_address_pairs(port_id)
@@ -543,8 +563,14 @@ def update_port(
543563
3) update_port(port_id, fixed_ips=current)
544564
545565
Notes:
546-
- For list-typed fields like security groups or allowed address pairs, this method replaces
547-
the entire list with the provided value. To remove all entries, pass an empty list [].
566+
- List-typed fields (security groups, allowed address pairs, fixed IPs) replace the entire list
567+
with the provided value. Pass [] to remove all entries.
568+
- For fixed IPs, each dict typically includes keys like "subnet_id" and/or "ip_address".
569+
570+
Examples:
571+
- Add a fixed IP: read current, append a new {"subnet_id": "subnet-2", "ip_address": "10.0.1.10"},
572+
then pass fixed_ips=[...]
573+
- Clear all security groups: security_group_ids=[]
548574
549575
:param port_id: ID of the port to update
550576
:param name: New port name
@@ -657,11 +683,16 @@ def create_floating_ip(
657683
"""
658684
Create a new Floating IP.
659685
686+
Typical use-cases:
687+
- Allocate in a pool and attach immediately: provide port_id (and optionally fixed_ip_address).
688+
- Allocate for later use: omit port_id (unassigned state).
689+
- Add metadata: provide description.
690+
660691
:param floating_network_id: External (floating) network ID
661-
:param description: Floating IP description
662-
:param fixed_ip_address: Internal fixed IP to map
663-
:param port_id: Port ID to attach
664-
:param project_id: Project ID
692+
:param description: Floating IP description (omit to keep empty)
693+
:param fixed_ip_address: Internal fixed IP to map when attaching to a port
694+
:param port_id: Port ID to attach (omit for unassigned allocation)
695+
:param project_id: Project ID to assign ownership
665696
:return: Created FloatingIP object
666697
"""
667698
conn = get_openstack_conn()
@@ -701,39 +732,46 @@ def attach_floating_ip_to_port(
701732
def update_floating_ip(
702733
self,
703734
floating_ip_id: str,
704-
description: str | None | object = _UNSET,
705-
port_id: str | None | object = _UNSET,
706-
fixed_ip_address: str | None | object = _UNSET,
735+
description: str | None = None,
736+
port_id: str | None = None,
737+
fixed_ip_address: str | None = None,
738+
clear_port: bool = False,
707739
) -> FloatingIP:
708740
"""
709741
Update Floating IP attributes. Only provided parameters are changed; omitted
710742
parameters remain untouched.
711743
712744
Typical use-cases:
713745
- Attach to a port: port_id="port-1" (optionally fixed_ip_address="10.0.0.10").
714-
- Detach from its port: port_id=None.
746+
- Detach from its port: clear_port=True and omit port_id (sets port_id=None).
747+
- Keep current port: clear_port=False and omit port_id.
715748
- Update description: description="new desc" or clear with description=None.
716749
- Reassign to another port: port_id="new-port" (optionally with fixed_ip_address).
717750
718751
Notes:
719752
- Passing None for description clears it.
720-
- Passing None for port_id detaches the address from any port.
753+
- clear_port controls whether to detach when no port_id is provided.
721754
- fixed_ip_address is optional and can be provided alongside port_id.
722755
723756
:param floating_ip_id: Floating IP ID to update
724-
:param description: New description or None to clear
725-
:param port_id: Port ID to attach; None to detach; omit to keep unchanged
726-
:param fixed_ip_address: Specific fixed IP to map; omit to keep unchanged
757+
:param description: New description (omit to keep unchanged, None to clear)
758+
:param port_id: Port ID to attach; omit to keep or detach depending on clear_port
759+
:param clear_port: If True and port_id is omitted, detach (set port_id=None); if False and
760+
port_id is omitted, keep current attachment
761+
:param fixed_ip_address: Specific fixed IP to map when attaching
727762
:return: Updated FloatingIP object
728763
"""
729764
conn = get_openstack_conn()
730765
update_args: dict = {}
731-
if description is not _UNSET:
766+
if description is not None:
732767
update_args["description"] = description
733-
if port_id is not _UNSET:
768+
if port_id is not None:
734769
update_args["port_id"] = port_id
735-
if fixed_ip_address is not _UNSET:
736-
update_args["fixed_ip_address"] = fixed_ip_address
770+
if fixed_ip_address is not None:
771+
update_args["fixed_ip_address"] = fixed_ip_address
772+
else:
773+
if clear_port:
774+
update_args["port_id"] = None
737775
if not update_args:
738776
current = conn.network.get_ip(floating_ip_id)
739777
return self._convert_to_floating_ip_model(current)

tests/tools/test_network_tools.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,7 @@ def test_set_and_clear_subnet_gateway(
11041104
assert res1.gateway_ip == "10.0.0.254"
11051105

11061106
updated.gateway_ip = None
1107-
res2 = tools.update_subnet("subnet-1", gateway_ip=None)
1107+
res2 = tools.update_subnet("subnet-1", clear_gateway=True)
11081108
assert res2.gateway_ip is None
11091109

11101110
def test_set_and_toggle_subnet_dhcp(
@@ -1238,7 +1238,7 @@ def test_create_attach_detach_delete_floating_ip(
12381238
assert attached.port_id == "port-1"
12391239

12401240
updated.port_id = None
1241-
detached = tools.update_floating_ip("fip-1", port_id=None)
1241+
detached = tools.update_floating_ip("fip-1", clear_port=True)
12421242
assert detached.port_id is None
12431243

12441244
mock_conn.network.get_ip.return_value = updated

0 commit comments

Comments
 (0)