Skip to content

Commit b06789c

Browse files
Milan FencikMilan Fencik
authored andcommitted
refactor
1 parent eca15aa commit b06789c

File tree

4 files changed

+306
-334
lines changed

4 files changed

+306
-334
lines changed

python/neutron-understack/neutron_understack/ml2_type_annotations.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ class PortContext:
145145
network: NetworkContext
146146
status: str
147147
original_status: str
148+
plugin_context: Any
148149
_plugin_context: Any
149150
_plugin: Any
150151
vif_type: str
Lines changed: 106 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import logging
2-
from typing import cast
3-
from uuid import UUID
42

53
from neutron.common.ovn import constants as ovn_const
64
from neutron.common.ovn import utils as ovn_utils
75
from neutron.conf.agent import ovs_conf
8-
from neutron.objects import base as base_obj
96
from neutron.objects.network import NetworkSegment
107
from neutron.objects.ports import Port
11-
from neutron.plugins.ml2 import db as ml2_db
8+
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.ovn_client import OVNClient
129
from neutron_lib import constants as p_const
1310
from neutron_lib import context as n_context
1411
from neutron_lib.api.definitions import segment as segment_def
@@ -24,51 +21,60 @@
2421
LOG = logging.getLogger(__name__)
2522

2623
ROUTER_INTERFACE_AND_GW = [
27-
p_const.DEVICE_OWNER_ROUTER_INTF, p_const.DEVICE_OWNER_ROUTER_GW
24+
p_const.DEVICE_OWNER_ROUTER_INTF,
25+
p_const.DEVICE_OWNER_ROUTER_GW,
2826
]
2927

3028

31-
def create_port_postcommit(context: PortContext):
32-
# When router port is created, we can end up in one of two situations:
33-
# 1. It's a first router port using the network
34-
# 2. There are already other routers that use this network
35-
#
36-
# In situation 1, we have to:
37-
# - create or find the dynamic network segment for network node
38-
# - create a segment-shared Neutron port that has network_id same as
39-
# router-specific port. Set the name to help identification.
40-
# - add the segment-shared port to a network node trunk
41-
# - create localnet port in OVN
42-
#
43-
# In situation 2, we don't have to do anything.
44-
if not is_first_port_on_network(context):
29+
def create_port_postcommit(context: PortContext) -> None:
30+
"""Router port creation logic.
31+
32+
When router port is created, we can end up in one of two situations:
33+
34+
1. It's a first router port using the network
35+
2. There are already other routers that use this network
36+
37+
In situation 1, we have to:
38+
- create or find the dynamic network segment for network node
39+
- create a segment-shared Neutron port that has network_id same as
40+
router-specific port. Set the name to help identification.
41+
- add the segment-shared port to a network node trunk
42+
- create localnet port in OVN
43+
44+
In situation 2, we don't have to do anything.
45+
"""
46+
network_id = context.current["network_id"]
47+
if not is_only_router_port_on_network(
48+
network_id=network_id, transaction_context=context.plugin_context
49+
):
4550
LOG.debug(
46-
"Creating a router port for a network that already has other routers."
51+
"Creating only a router port %(port)s for a network %(network)s "
52+
"as there are already other routers on the same network.",
53+
{"port": context.current["id"], "network": network_id},
4754
)
4855
return
4956

50-
segment = _existing_segment(context) or create_router_segment(context)
51-
network_id = context.current["network_id"]
57+
segment = fetch_or_create_router_segment(context)
5258

5359
# Trunk
5460
shared_port = utils.create_neutron_port_for_segment(segment, context)
5561
add_subport_to_trunk(shared_port, segment, context)
5662

5763
# OVN
5864
segment_obj = utils.network_segment_by_id(segment["id"])
59-
create_uplink_port(segment_obj, str(network_id))
65+
create_uplink_port(segment_obj, network_id)
6066

6167

62-
def is_first_port_on_network(context: PortContext) -> bool:
63-
network_id = context.current["network_id"]
68+
def is_only_router_port_on_network(
69+
network_id: str,
70+
transaction_context: n_context.Context | None = None,
71+
) -> bool:
72+
transaction_context = transaction_context or n_context.get_admin_context()
6473

6574
other_router_ports = Port.get_objects(
66-
context.plugin_context,
75+
transaction_context,
6776
network_id=network_id,
68-
device_owner=[
69-
p_const.DEVICE_OWNER_ROUTER_INTF,
70-
p_const.DEVICE_OWNER_ROUTER_GW,
71-
],
77+
device_owner=ROUTER_INTERFACE_AND_GW,
7278
)
7379

7480
LOG.debug("Router ports found: %(ports)s", {"ports": other_router_ports})
@@ -78,25 +84,9 @@ def is_first_port_on_network(context: PortContext) -> bool:
7884
return True
7985

8086

81-
def _existing_segment(context) -> NetworkSegmentDict | None:
82-
filter = {
83-
"network_id": context.current["network_id"],
84-
"network_type": p_const.TYPE_VLAN,
85-
"physical_network": cfg.CONF.ml2_understack.network_node_switchport_physnet,
86-
}
87-
admin_context = n_context.get_admin_context()
88-
matching_segments = NetworkSegment.get_objects(
89-
admin_context, _pager=base_obj.Pager(limit=1), **filter
90-
)
91-
if matching_segments:
92-
return cast(NetworkSegmentDict, matching_segments[0].to_dict())
93-
else:
94-
return None
95-
96-
9787
def add_subport_to_trunk(
9888
shared_port: PortDict, segment: NetworkSegmentDict, context: PortContext
99-
):
89+
) -> None:
10090
"""Adds requested port as a subport of a trunk connection for network nodes.
10191
10292
The trunk and parent port must already exist.
@@ -110,30 +100,34 @@ def add_subport_to_trunk(
110100
},
111101
]
112102
}
113-
return utils.fetch_trunk_plugin().add_subports(
103+
utils.fetch_trunk_plugin().add_subports(
114104
context=context.plugin_context,
115105
trunk_id=cfg.CONF.ml2_understack.network_node_trunk_uuid,
116106
subports=subports,
117107
)
118108

119109

120-
def create_router_segment(context: PortContext) -> NetworkSegmentDict:
121-
"""Creates a dynamic segment for connection between the router and network node."""
122-
network_id = UUID(context.current["network_id"])
110+
def fetch_or_create_router_segment(context: PortContext) -> NetworkSegmentDict:
111+
"""Get or create a dynamic segment.
112+
113+
allocate_dynamic_segment will get or create a segment for connection between
114+
the router and network node.
115+
"""
116+
network_id = context.current["network_id"]
123117
physnet = cfg.CONF.ml2_understack.network_node_switchport_physnet
124118
if not physnet:
125119
raise ValueError(
126120
"please configure ml2_understack.network_node_switchport_physnet"
127121
)
128122
segment = utils.allocate_dynamic_segment(
129-
network_id=str(network_id),
123+
network_id=network_id,
130124
physnet=physnet,
131125
)
132126
if not segment:
133127
raise Exception(
134128
"failed allocating dynamic segment for"
135129
"network_id=%(network_id)s physnet=%(physnet)s",
136-
{"network_id": str(network_id), "physnet": physnet},
130+
{"network_id": network_id, "physnet": physnet},
137131
)
138132
LOG.debug("router dynamic segment: %(segment)s", {"segment": segment})
139133
return segment
@@ -142,7 +136,7 @@ def create_router_segment(context: PortContext) -> NetworkSegmentDict:
142136
_cached_ovn_client = None
143137

144138

145-
def ovn_client():
139+
def ovn_client() -> OVNClient | None:
146140
"""Retrieve the OVN client from the OVN ML2 plugin."""
147141
global _cached_ovn_client
148142
if _cached_ovn_client:
@@ -171,7 +165,7 @@ def ovn_client():
171165
return _cached_ovn_client
172166

173167

174-
def create_uplink_port(segment: NetworkSegment, network_id: str, txn=None):
168+
def create_uplink_port(segment: NetworkSegment, network_id: str, txn=None) -> None:
175169
"""Create a localnet port to connect given NetworkSegment to a network node."""
176170
tag = segment.get(segment_def.SEGMENTATION_ID, [])
177171
physnet = segment.get(segment_def.PHYSICAL_NETWORK)
@@ -194,132 +188,67 @@ def create_uplink_port(segment: NetworkSegment, network_id: str, txn=None):
194188
ovn_client()._transaction([cmd], txn=txn)
195189

196190

197-
def delete_uplink_port(segment: NetworkSegment, network_id: str):
191+
def delete_uplink_port(segment: NetworkSegment, network_id: str) -> None:
198192
"""Remove a localnet uplink port from a network node."""
199193
port_to_del = f"uplink-{segment['id']}"
200194
cmd = ovn_client()._nb_idl.delete_lswitch_port(
201195
lport_name=port_to_del, lswitch_name=ovn_utils.ovn_name(network_id)
202196
)
203-
return ovn_client()._transaction([cmd])
197+
ovn_client()._transaction([cmd])
204198

205199

206200
def handle_router_interface_removal(_resource, _event, trigger, payload) -> None:
207201
"""Handles the removal of a router interface.
208202
209203
When router interface port is deleted, we remove the corresponding subport
210204
from the trunk and delete OVN localnet port.
211-
"""
212-
# We have router-specific port that is being deleted.
213-
# We have segment-shared port for shared networks.
214-
#
215-
# When the delete router port event is received, we can be in two situations:
216-
# 1. The port is the last one that uses shared network.
217-
# 2. The port is being detached from a network, but there are other routers
218-
# still using that network.
219-
#
220-
# In situation 1, we have to:
221-
# - identify segment-shared Neutron port. This can be done by looking up
222-
# ports that are subports of the preconfigured "network node" trunk with
223-
# matching segmentation_id and network_id.
224-
# - remove the segment-shared Neutron port from a trunk
225-
# - remove the localnet port in OVN for same segmentation_id/VLAN
226-
# - delete the segment-shared Neutron port
227-
#
228-
# In situation 2, we don't have to do nothing. Router-specific port gets
229-
# deleted by Neutron and segment-shared port stays around.
230205
206+
We have router-specific port that is being deleted.
207+
We have segment-shared port for shared networks.
208+
209+
When the delete router port event is received, we can be in two situations:
210+
1. The port is the last one that uses shared network.
211+
2. The port is being detached from a network, but there are other routers
212+
still using that network.
213+
214+
In situation 1, we have to:
215+
- identify segment-shared Neutron port. This is done by looking up
216+
ports by name in format uplink-<segment_id>.
217+
- remove the segment-shared Neutron port from a trunk
218+
- remove the localnet port in OVN for same segmentation_id/VLAN
219+
- delete the segment-shared Neutron port
220+
221+
In situation 2, we don't have to do anything. Router-specific port gets
222+
deleted by Neutron and segment-shared port stays around.
223+
"""
231224
port = payload.metadata["port"]
225+
network_id = port["network_id"]
232226

233227
if port["device_owner"] not in ROUTER_INTERFACE_AND_GW:
234228
return
235229

236-
if not is_last_port_on_network(port):
230+
if not is_only_router_port_on_network(network_id):
237231
LOG.debug(
238232
"Deleting only Router port %(port)s as there are other"
239-
" router ports using the same network", {"port": port}
233+
" router ports using the same network",
234+
{"port": port},
240235
)
241236
return
242237

243-
network_id = port["network_id"]
244-
245-
segment = utils.network_segment_by_physnet(
246-
network_id,
247-
cfg.CONF.ml2_understack.network_node_switchport_physnet
248-
)
238+
segment = fetch_router_network_segment(network_id)
249239
if not segment:
250-
LOG.error(
251-
"Router network segment not found for network %(network_id)s",
252-
{"network_id": network_id}
253-
)
254240
return
255241

256-
LOG.debug("Router network segment found %(segment)s", {"segment": segment})
257-
shared_ports = Port.get_objects(
258-
n_context.get_admin_context(),
259-
name=f"uplink-{segment['id']}"
260-
)
261-
262-
if not shared_ports:
263-
LOG.error(
264-
"No router shared ports found for segment %(segment)s",
265-
{"segment", segment}
266-
)
242+
shared_port = fetch_shared_router_port(segment)
243+
if not shared_port:
267244
return
268-
LOG.debug("Router shared ports found %(ports)s", {"ports": shared_ports})
269-
270-
shared_port = shared_ports[0]
271245

272-
_handle_subport_removal(shared_port)
246+
handle_subport_removal(shared_port)
273247
delete_uplink_port(segment, network_id)
274248
shared_port.delete()
275249

276250

277-
def is_last_port_on_network(port: PortDict) -> bool:
278-
network_id = port["network_id"]
279-
280-
other_router_ports = Port.get_objects(
281-
n_context.get_admin_context(),
282-
network_id=network_id,
283-
device_owner=ROUTER_INTERFACE_AND_GW,
284-
)
285-
286-
LOG.debug("Router ports found: %(ports)s", {"ports": other_router_ports})
287-
if len(other_router_ports) > 1:
288-
return False
289-
else:
290-
return True
291-
292-
293-
# def _handle_localnet_port_removal(port):
294-
# """Removes OVN localnet port that is used for this trunked VLAN."""
295-
# admin_context = n_context.get_admin_context()
296-
# try:
297-
# parent_port_id = port["binding:profile"]["parent_name"]
298-
# except KeyError as err:
299-
# LOG.error(
300-
# "Port %(port)s is not added to a trunk. %(err)",
301-
# {"port": port["id"], "err": err},
302-
# )
303-
# return
304-
305-
# parent_port = utils.fetch_port_object(parent_port_id)
306-
# binding_host = parent_port.bindings[0].host
307-
308-
# binding_levels = ml2_db.get_binding_level_objs(
309-
# admin_context, port["id"], binding_host
310-
# )
311-
312-
# LOG.debug("binding_levels: %(lvls)s", {"lvls": binding_levels})
313-
314-
# if binding_levels:
315-
# segment_id = binding_levels[-1].segment_id
316-
# LOG.debug("looking up segment_id: %s", segment_id)
317-
# segment_obj = utils.network_segment_by_id(segment_id)
318-
# # ovn_client().delete_provnet_port(port["network_id"], segment_obj)
319-
# delete_uplink_port(segment_obj, port["network_id"])
320-
321-
322-
def _handle_subport_removal(port: Port):
251+
def handle_subport_removal(port: Port) -> None:
323252
"""Removes router's subport from a network node trunk."""
324253
# trunk_id will be discovered dynamically at some point
325254
trunk_id = cfg.CONF.ml2_understack.network_node_trunk_uuid
@@ -329,3 +258,31 @@ def _handle_subport_removal(port: Port):
329258
utils.remove_subport_from_trunk(trunk_id, port_id)
330259
except Exception as err:
331260
LOG.error("failed removing_subport: %(error)s", {"error": err})
261+
262+
263+
def fetch_router_network_segment(network_id: str) -> NetworkSegment | None:
264+
segment = utils.network_segment_by_physnet(
265+
network_id, cfg.CONF.ml2_understack.network_node_switchport_physnet
266+
)
267+
if not segment:
268+
LOG.error(
269+
"Router network segment not found for network %(network_id)s",
270+
{"network_id": network_id},
271+
)
272+
return
273+
LOG.debug("Router network segment found %(segment)s", {"segment": segment})
274+
return segment
275+
276+
277+
def fetch_shared_router_port(segment: NetworkSegment) -> Port | None:
278+
shared_ports = Port.get_objects(
279+
n_context.get_admin_context(), name=f"uplink-{segment['id']}"
280+
)
281+
282+
if not shared_ports:
283+
LOG.error(
284+
"No router shared ports found for segment %(segment)s", {"segment", segment}
285+
)
286+
return
287+
LOG.debug("Router shared ports found %(ports)s", {"ports": shared_ports})
288+
return shared_ports[0]

0 commit comments

Comments
 (0)