|
1 | 1 | from fastmcp import FastMCP |
2 | 2 |
|
3 | 3 | from .base import get_openstack_conn |
| 4 | +from .request.network import ( |
| 5 | + ExternalGatewayInfo, |
| 6 | + Route, |
| 7 | +) |
4 | 8 | from .response.network import ( |
5 | 9 | FloatingIP, |
6 | 10 | Network, |
7 | 11 | Port, |
| 12 | + Router, |
8 | 13 | Subnet, |
9 | 14 | ) |
10 | 15 |
|
@@ -80,6 +85,7 @@ def create_network( |
80 | 85 | provider_network_type: str | None = None, |
81 | 86 | provider_physical_network: str | None = None, |
82 | 87 | provider_segmentation_id: int | None = None, |
| 88 | + project_id: str | None = None, |
83 | 89 | ) -> Network: |
84 | 90 | """ |
85 | 91 | Create a new Network. |
@@ -107,6 +113,9 @@ def create_network( |
107 | 113 | if provider_network_type: |
108 | 114 | network_args["provider_network_type"] = provider_network_type |
109 | 115 |
|
| 116 | + if project_id: |
| 117 | + network_args["project_id"] = project_id |
| 118 | + |
110 | 119 | if provider_physical_network: |
111 | 120 | network_args["provider_physical_network"] = ( |
112 | 121 | provider_physical_network |
@@ -860,3 +869,176 @@ def _convert_to_floating_ip_model(self, openstack_ip) -> FloatingIP: |
860 | 869 | port_id=openstack_ip.port_id, |
861 | 870 | router_id=openstack_ip.router_id, |
862 | 871 | ) |
| 872 | + |
| 873 | + def get_routers( |
| 874 | + self, |
| 875 | + status_filter: str | None = None, |
| 876 | + project_id: str | None = None, |
| 877 | + is_admin_state_up: bool | None = None, |
| 878 | + ) -> list[Router]: |
| 879 | + """ |
| 880 | + Get the list of Routers with optional filtering. |
| 881 | + :param status_filter: Filter by router status (e.g., `ACTIVE`, `DOWN`) |
| 882 | + :param project_id: Filter by project ID |
| 883 | + :param is_admin_state_up: Filter by admin state |
| 884 | + :return: List of Router objects |
| 885 | + """ |
| 886 | + conn = get_openstack_conn() |
| 887 | + filters: dict = {} |
| 888 | + if status_filter: |
| 889 | + filters["status"] = status_filter.upper() |
| 890 | + if project_id: |
| 891 | + filters["project_id"] = project_id |
| 892 | + if is_admin_state_up is not None: |
| 893 | + filters["admin_state_up"] = is_admin_state_up |
| 894 | + routers = conn.list_routers(filters=filters) |
| 895 | + return [self._convert_to_router_model(r) for r in routers] |
| 896 | + |
| 897 | + def create_router( |
| 898 | + self, |
| 899 | + name: str | None = None, |
| 900 | + description: str | None = None, |
| 901 | + is_admin_state_up: bool = True, |
| 902 | + is_distributed: bool | None = None, |
| 903 | + project_id: str | None = None, |
| 904 | + external_gateway_info: ExternalGatewayInfo | None = None, |
| 905 | + ) -> Router: |
| 906 | + """ |
| 907 | + Create a new Router. |
| 908 | + Typical use-cases: |
| 909 | + - Create basic router: name="r1" (defaults to admin_state_up=True) |
| 910 | + - Create distributed router: is_distributed=True |
| 911 | + - Create with external gateway for north-south traffic: |
| 912 | + external_gateway_info={"network_id": "ext-net", "enable_snat": True, |
| 913 | + "external_fixed_ips": [{"subnet_id": "ext-subnet", "ip_address": "203.0.113.10"}]} |
| 914 | + - Create with project ownership: project_id="proj-1" |
| 915 | + Notes: |
| 916 | + - external_gateway_info should follow Neutron schema: at minimum include |
| 917 | + "network_id"; optional keys include "enable_snat" and "external_fixed_ips". |
| 918 | + :param name: Router name |
| 919 | + :param description: Router description |
| 920 | + :param is_admin_state_up: Administrative state |
| 921 | + :param is_distributed: Distributed router flag |
| 922 | + :param project_id: Project ownership |
| 923 | + :param external_gateway_info: External gateway info dict |
| 924 | + :return: Created Router object |
| 925 | + """ |
| 926 | + conn = get_openstack_conn() |
| 927 | + router_args: dict = {"admin_state_up": is_admin_state_up} |
| 928 | + if name is not None: |
| 929 | + router_args["name"] = name |
| 930 | + if description is not None: |
| 931 | + router_args["description"] = description |
| 932 | + if is_distributed is not None: |
| 933 | + router_args["distributed"] = is_distributed |
| 934 | + if project_id is not None: |
| 935 | + router_args["project_id"] = project_id |
| 936 | + if external_gateway_info is not None: |
| 937 | + router_args["external_gateway_info"] = ( |
| 938 | + external_gateway_info.model_dump(exclude_none=True) |
| 939 | + ) |
| 940 | + router = conn.network.create_router(**router_args) |
| 941 | + return self._convert_to_router_model(router) |
| 942 | + |
| 943 | + def get_router_detail(self, router_id: str) -> Router: |
| 944 | + """ |
| 945 | + Get detailed information about a specific Router. |
| 946 | + :param router_id: ID of the router to retrieve |
| 947 | + :return: Router details |
| 948 | + """ |
| 949 | + conn = get_openstack_conn() |
| 950 | + router = conn.network.get_router(router_id) |
| 951 | + return self._convert_to_router_model(router) |
| 952 | + |
| 953 | + def update_router( |
| 954 | + self, |
| 955 | + router_id: str, |
| 956 | + name: str | None = None, |
| 957 | + description: str | None = None, |
| 958 | + is_admin_state_up: bool | None = None, |
| 959 | + is_distributed: bool | None = None, |
| 960 | + external_gateway_info: ExternalGatewayInfo | None = None, |
| 961 | + clear_external_gateway: bool = False, |
| 962 | + routes: list[Route] | None = None, |
| 963 | + ) -> Router: |
| 964 | + """ |
| 965 | + Update Router attributes atomically. Only provided parameters are changed; |
| 966 | + omitted parameters remain untouched. |
| 967 | + Typical use-cases: |
| 968 | + - Rename and change description: name="r-new", description="d". |
| 969 | + - Toggle admin state: read current via get_router_detail(); pass inverted bool to is_admin_state_up. |
| 970 | + - Set distributed flag: is_distributed=True or False. |
| 971 | + - Set external gateway: external_gateway_info={"network_id": "ext-net", "enable_snat": True, "external_fixed_ips": [...]}. |
| 972 | + - Clear external gateway: clear_external_gateway=True (takes precedence over external_gateway_info). |
| 973 | + - Replace static routes: routes=[{"destination": "192.0.2.0/24", "nexthop": "10.0.0.1"}]. Pass [] to remove all routes. |
| 974 | + Notes: |
| 975 | + - For list-typed fields (routes), the provided list replaces the entire list on the server. |
| 976 | + - To clear external gateway, use clear_external_gateway=True. If both provided, clear_external_gateway takes precedence. |
| 977 | + :param router_id: ID of the router to update |
| 978 | + :param name: New router name |
| 979 | + :param description: New router description |
| 980 | + :param is_admin_state_up: Administrative state |
| 981 | + :param is_distributed: Distributed router flag |
| 982 | + :param external_gateway_info: External gateway info dict to set |
| 983 | + :param clear_external_gateway: If True, clear external gateway (set to None) |
| 984 | + :param routes: Static routes (replaces entire list) |
| 985 | + :return: Updated Router object |
| 986 | + """ |
| 987 | + conn = get_openstack_conn() |
| 988 | + update_args: dict = {} |
| 989 | + if name is not None: |
| 990 | + update_args["name"] = name |
| 991 | + if description is not None: |
| 992 | + update_args["description"] = description |
| 993 | + if is_admin_state_up is not None: |
| 994 | + update_args["admin_state_up"] = is_admin_state_up |
| 995 | + if is_distributed is not None: |
| 996 | + update_args["distributed"] = is_distributed |
| 997 | + if clear_external_gateway: |
| 998 | + update_args["external_gateway_info"] = None |
| 999 | + elif external_gateway_info is not None: |
| 1000 | + update_args["external_gateway_info"] = ( |
| 1001 | + external_gateway_info.model_dump(exclude_none=True) |
| 1002 | + ) |
| 1003 | + if routes is not None: |
| 1004 | + update_args["routes"] = [ |
| 1005 | + r.model_dump(exclude_none=True) for r in routes |
| 1006 | + ] |
| 1007 | + if not update_args: |
| 1008 | + current = conn.network.get_router(router_id) |
| 1009 | + return self._convert_to_router_model(current) |
| 1010 | + router = conn.network.update_router(router_id, **update_args) |
| 1011 | + return self._convert_to_router_model(router) |
| 1012 | + |
| 1013 | + def delete_router(self, router_id: str) -> None: |
| 1014 | + """ |
| 1015 | + Delete a Router. |
| 1016 | + :param router_id: ID of the router to delete |
| 1017 | + :return: None |
| 1018 | + """ |
| 1019 | + conn = get_openstack_conn() |
| 1020 | + conn.network.delete_router(router_id, ignore_missing=False) |
| 1021 | + return None |
| 1022 | + |
| 1023 | + def _convert_to_router_model(self, openstack_router) -> Router: |
| 1024 | + """ |
| 1025 | + Convert an OpenStack Router object to a Router pydantic model. |
| 1026 | + :param openstack_router: OpenStack router object |
| 1027 | + :return: Pydantic Router model |
| 1028 | + """ |
| 1029 | + return Router( |
| 1030 | + id=openstack_router.id, |
| 1031 | + name=getattr(openstack_router, "name", None), |
| 1032 | + status=getattr(openstack_router, "status", None), |
| 1033 | + description=getattr(openstack_router, "description", None), |
| 1034 | + project_id=getattr(openstack_router, "project_id", None), |
| 1035 | + is_admin_state_up=getattr( |
| 1036 | + openstack_router, "is_admin_state_up", None |
| 1037 | + ), |
| 1038 | + external_gateway_info=getattr( |
| 1039 | + openstack_router, "external_gateway_info", None |
| 1040 | + ), |
| 1041 | + is_distributed=getattr(openstack_router, "is_distributed", None), |
| 1042 | + is_ha=getattr(openstack_router, "is_ha", None), |
| 1043 | + routes=getattr(openstack_router, "routes", None), |
| 1044 | + ) |
0 commit comments