1
1
import logging
2
- from typing import cast
3
- from uuid import UUID
4
2
5
3
from neutron .common .ovn import constants as ovn_const
6
4
from neutron .common .ovn import utils as ovn_utils
7
5
from neutron .conf .agent import ovs_conf
8
- from neutron .objects import base as base_obj
9
6
from neutron .objects .network import NetworkSegment
10
7
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
12
9
from neutron_lib import constants as p_const
13
10
from neutron_lib import context as n_context
14
11
from neutron_lib .api .definitions import segment as segment_def
24
21
LOG = logging .getLogger (__name__ )
25
22
26
23
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 ,
28
26
]
29
27
30
28
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
+ ):
45
50
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 },
47
54
)
48
55
return
49
56
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 )
52
58
53
59
# Trunk
54
60
shared_port = utils .create_neutron_port_for_segment (segment , context )
55
61
add_subport_to_trunk (shared_port , segment , context )
56
62
57
63
# OVN
58
64
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 )
60
66
61
67
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 ()
64
73
65
74
other_router_ports = Port .get_objects (
66
- context . plugin_context ,
75
+ transaction_context ,
67
76
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 ,
72
78
)
73
79
74
80
LOG .debug ("Router ports found: %(ports)s" , {"ports" : other_router_ports })
@@ -78,25 +84,9 @@ def is_first_port_on_network(context: PortContext) -> bool:
78
84
return True
79
85
80
86
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
-
97
87
def add_subport_to_trunk (
98
88
shared_port : PortDict , segment : NetworkSegmentDict , context : PortContext
99
- ):
89
+ ) -> None :
100
90
"""Adds requested port as a subport of a trunk connection for network nodes.
101
91
102
92
The trunk and parent port must already exist.
@@ -110,30 +100,34 @@ def add_subport_to_trunk(
110
100
},
111
101
]
112
102
}
113
- return utils .fetch_trunk_plugin ().add_subports (
103
+ utils .fetch_trunk_plugin ().add_subports (
114
104
context = context .plugin_context ,
115
105
trunk_id = cfg .CONF .ml2_understack .network_node_trunk_uuid ,
116
106
subports = subports ,
117
107
)
118
108
119
109
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" ]
123
117
physnet = cfg .CONF .ml2_understack .network_node_switchport_physnet
124
118
if not physnet :
125
119
raise ValueError (
126
120
"please configure ml2_understack.network_node_switchport_physnet"
127
121
)
128
122
segment = utils .allocate_dynamic_segment (
129
- network_id = str ( network_id ) ,
123
+ network_id = network_id ,
130
124
physnet = physnet ,
131
125
)
132
126
if not segment :
133
127
raise Exception (
134
128
"failed allocating dynamic segment for"
135
129
"network_id=%(network_id)s physnet=%(physnet)s" ,
136
- {"network_id" : str ( network_id ) , "physnet" : physnet },
130
+ {"network_id" : network_id , "physnet" : physnet },
137
131
)
138
132
LOG .debug ("router dynamic segment: %(segment)s" , {"segment" : segment })
139
133
return segment
@@ -142,7 +136,7 @@ def create_router_segment(context: PortContext) -> NetworkSegmentDict:
142
136
_cached_ovn_client = None
143
137
144
138
145
- def ovn_client ():
139
+ def ovn_client () -> OVNClient | None :
146
140
"""Retrieve the OVN client from the OVN ML2 plugin."""
147
141
global _cached_ovn_client
148
142
if _cached_ovn_client :
@@ -171,7 +165,7 @@ def ovn_client():
171
165
return _cached_ovn_client
172
166
173
167
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 :
175
169
"""Create a localnet port to connect given NetworkSegment to a network node."""
176
170
tag = segment .get (segment_def .SEGMENTATION_ID , [])
177
171
physnet = segment .get (segment_def .PHYSICAL_NETWORK )
@@ -194,132 +188,67 @@ def create_uplink_port(segment: NetworkSegment, network_id: str, txn=None):
194
188
ovn_client ()._transaction ([cmd ], txn = txn )
195
189
196
190
197
- def delete_uplink_port (segment : NetworkSegment , network_id : str ):
191
+ def delete_uplink_port (segment : NetworkSegment , network_id : str ) -> None :
198
192
"""Remove a localnet uplink port from a network node."""
199
193
port_to_del = f"uplink-{ segment ['id' ]} "
200
194
cmd = ovn_client ()._nb_idl .delete_lswitch_port (
201
195
lport_name = port_to_del , lswitch_name = ovn_utils .ovn_name (network_id )
202
196
)
203
- return ovn_client ()._transaction ([cmd ])
197
+ ovn_client ()._transaction ([cmd ])
204
198
205
199
206
200
def handle_router_interface_removal (_resource , _event , trigger , payload ) -> None :
207
201
"""Handles the removal of a router interface.
208
202
209
203
When router interface port is deleted, we remove the corresponding subport
210
204
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.
230
205
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
+ """
231
224
port = payload .metadata ["port" ]
225
+ network_id = port ["network_id" ]
232
226
233
227
if port ["device_owner" ] not in ROUTER_INTERFACE_AND_GW :
234
228
return
235
229
236
- if not is_last_port_on_network ( port ):
230
+ if not is_only_router_port_on_network ( network_id ):
237
231
LOG .debug (
238
232
"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 },
240
235
)
241
236
return
242
237
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 )
249
239
if not segment :
250
- LOG .error (
251
- "Router network segment not found for network %(network_id)s" ,
252
- {"network_id" : network_id }
253
- )
254
240
return
255
241
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 :
267
244
return
268
- LOG .debug ("Router shared ports found %(ports)s" , {"ports" : shared_ports })
269
-
270
- shared_port = shared_ports [0 ]
271
245
272
- _handle_subport_removal (shared_port )
246
+ handle_subport_removal (shared_port )
273
247
delete_uplink_port (segment , network_id )
274
248
shared_port .delete ()
275
249
276
250
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 :
323
252
"""Removes router's subport from a network node trunk."""
324
253
# trunk_id will be discovered dynamically at some point
325
254
trunk_id = cfg .CONF .ml2_understack .network_node_trunk_uuid
@@ -329,3 +258,31 @@ def _handle_subport_removal(port: Port):
329
258
utils .remove_subport_from_trunk (trunk_id , port_id )
330
259
except Exception as err :
331
260
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