Merge ~chris.macnaughton/ubuntu/+source/neutron:bug/1881157 into ~ubuntu-server-dev/ubuntu/+source/neutron:stable/queens
- Git
- lp:~chris.macnaughton/ubuntu/+source/neutron
- bug/1881157
- Merge into stable/queens
Proposed by
Chris MacNaughton
Status: | Needs review |
---|---|
Proposed branch: | ~chris.macnaughton/ubuntu/+source/neutron:bug/1881157 |
Merge into: | ~ubuntu-server-dev/ubuntu/+source/neutron:stable/queens |
Diff against target: |
425 lines (+403/-0) 3 files modified
debian/changelog (+7/-0) debian/patches/lp1881157-remote-sg-is-left-behind.patch (+395/-0) debian/patches/series (+1/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Corey Bryant | Approve | ||
Review via email: mp+391122@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Corey Bryant (corey.bryant) wrote : | # |
Diff with stable/queens patch looks good. Looks like there was a quilt refresh to adapt to the current state of the package branch. Approving for now but not merging yet.
Revision history for this message
Chris MacNaughton (chris.macnaughton) wrote : | # |
Agreed - ideally we'll get that neutron point release out soon which will give us S/T/U more easily!
Unmerged commits
- 0489eea... by Chris MacNaughton
-
d/p/lp1881157-
remote- sg-is-left- behind. patch: Backport fix for security group IDs being left behind (LP: #1881157).
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/debian/changelog b/debian/changelog | |||
2 | index 75f60a5..70e65db 100644 | |||
3 | --- a/debian/changelog | |||
4 | +++ b/debian/changelog | |||
5 | @@ -1,3 +1,10 @@ | |||
6 | 1 | neutron (2:12.1.1-0ubuntu3) UNRELEASED; urgency=medium | ||
7 | 2 | |||
8 | 3 | * d/p/lp1881157-remote-sg-is-left-behind.patch: Backport fix for security | ||
9 | 4 | group IDs being left behind (LP: #1881157). | ||
10 | 5 | |||
11 | 6 | -- Chris MacNaughton <chris.macnaughton@canonical.com> Tue, 22 Sep 2020 11:16:27 +0000 | ||
12 | 7 | |||
13 | 1 | neutron (2:12.1.1-0ubuntu2) bionic; urgency=medium | 8 | neutron (2:12.1.1-0ubuntu2) bionic; urgency=medium |
14 | 2 | 9 | ||
15 | 3 | * d/p/Ensure-fip-ip-rules-deleted-when-fip-removed.patch | 10 | * d/p/Ensure-fip-ip-rules-deleted-when-fip-removed.patch |
16 | diff --git a/debian/patches/lp1881157-remote-sg-is-left-behind.patch b/debian/patches/lp1881157-remote-sg-is-left-behind.patch | |||
17 | 4 | new file mode 100644 | 11 | new file mode 100644 |
18 | index 0000000..fda14bc | |||
19 | --- /dev/null | |||
20 | +++ b/debian/patches/lp1881157-remote-sg-is-left-behind.patch | |||
21 | @@ -0,0 +1,395 @@ | |||
22 | 1 | From d0b696f076a94830b040ffcb2dec28e2eabe2e04 Mon Sep 17 00:00:00 2001 | ||
23 | 2 | From: Rodolfo Alonso Hernandez <ralonsoh@redhat.com> | ||
24 | 3 | Date: Tue, 02 Jun 2020 17:09:07 +0000 | ||
25 | 4 | Subject: [PATCH] [OVS][FW] Remote SG IDs left behind when a SG is removed | ||
26 | 5 | |||
27 | 6 | When any port in the OVS agent is using a security groups (SG) and | ||
28 | 7 | this SG is removed, is marked to be deleted. This deletion process | ||
29 | 8 | is done in [1]. | ||
30 | 9 | |||
31 | 10 | The SG deletion process consists on removing any reference of this SG | ||
32 | 11 | from the firewall and the SG port map. The firewall removes this SG in | ||
33 | 12 | [2]. | ||
34 | 13 | |||
35 | 14 | The information of a SG is stored in: | ||
36 | 15 | * ConjIPFlowManager.conj_id_map = ConjIdMap(). This class stores the | ||
37 | 16 | conjunction IDS (conj_ids) in a dictionary using the following keys: | ||
38 | 17 | |||
39 | 18 | ConjIdMap.id_map[(sg_id, remote_sg_id, direction, ethertype, | ||
40 | 19 | conj_ids)] = conj_id_XXX | ||
41 | 20 | |||
42 | 21 | * ConjIPFlowManager.conj_ids is a nested dictionary, built in the | ||
43 | 22 | following way: | ||
44 | 23 | |||
45 | 24 | self.conj_ids[vlan_tag][(direction, ethertype)][remote_sg_id] = \ | ||
46 | 25 | set([conj_id_1, conj_id_2, ...]) | ||
47 | 26 | |||
48 | 27 | This patch stores all conjuntion IDs generated and assigned to the | ||
49 | 28 | tuple (sg_id, remote_sg_id, direction, ethertype). When a SG is | ||
50 | 29 | removed, the deletion method will look for this SG in the new storage | ||
51 | 30 | variable created, ConjIdMap.id_map_group, and will mark all the | ||
52 | 31 | conjuntion IDs related to be removed. That will cleanup those rules | ||
53 | 32 | left in the OVS matching: | ||
54 | 33 | action=conjunction(conj_id, 1/2) | ||
55 | 34 | |||
56 | 35 | [1]https://github.com/openstack/neutron/blob/118930f03d31f157f8c7a9e6c57122ecea8982b9/neutron/agent/linux/openvswitch_firewall/firewall.py#L731 | ||
57 | 36 | [2]https://github.com/openstack/neutron/blob/118930f03d31f157f8c7a9e6c57122ecea8982b9/neutron/agent/linux/openvswitch_firewall/firewall.py#L399 | ||
58 | 37 | |||
59 | 38 | Conflicts: | ||
60 | 39 | neutron/agent/linux/openvswitch_firewall/firewall.py | ||
61 | 40 | neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py | ||
62 | 41 | |||
63 | 42 | Change-Id: I63e446a30cf10e7bcd34a6f0d6ba1711301efcbe | ||
64 | 43 | Related-Bug: #1881157 | ||
65 | 44 | (cherry picked from commit 0eebd002ccda66dc6d9f9e5a254815109225e299) | ||
66 | 45 | (cherry picked from commit ed22f7a2ff19a874bc8521f84cb4fd1c7483a23f) | ||
67 | 46 | (cherry picked from commit 6615f248e25a361d988db1795a3486a58f4768cb) | ||
68 | 47 | --- | ||
69 | 48 | |||
70 | 49 | diff --git a/neutron/agent/linux/openvswitch_firewall/firewall.py b/neutron/agent/linux/openvswitch_firewall/firewall.py | ||
71 | 50 | index 3570032..b8522b2 100644 | ||
72 | 51 | --- a/neutron/agent/linux/openvswitch_firewall/firewall.py | ||
73 | 52 | +++ b/neutron/agent/linux/openvswitch_firewall/firewall.py | ||
74 | 53 | @@ -268,6 +268,10 @@ | ||
75 | 54 | |||
76 | 55 | def __init__(self): | ||
77 | 56 | self.id_map = collections.defaultdict(self._conj_id_factory) | ||
78 | 57 | + # Stores the set of conjuntion IDs used for each unique tuple | ||
79 | 58 | + # (sg_id, remote_sg_id, direction, ethertype). Each tuple can have up | ||
80 | 59 | + # to 8 conjuntion IDs (see ConjIPFlowManager.add()). | ||
81 | 60 | + self.id_map_group = collections.defaultdict(set) | ||
82 | 61 | self.id_free = collections.deque() | ||
83 | 62 | self.max_id = 0 | ||
84 | 63 | |||
85 | 64 | @@ -298,12 +302,21 @@ | ||
86 | 65 | return a list of (remote_sg_id, conj_id), which are no longer | ||
87 | 66 | in use. | ||
88 | 67 | """ | ||
89 | 68 | - result = [] | ||
90 | 69 | + result = set([]) | ||
91 | 70 | for k in list(self.id_map.keys()): | ||
92 | 71 | if sg_id in k[0:2]: | ||
93 | 72 | conj_id = self.id_map.pop(k) | ||
94 | 73 | - result.append((k[1], conj_id)) | ||
95 | 74 | + result.add((k[1], conj_id)) | ||
96 | 75 | self.id_free.append(conj_id) | ||
97 | 76 | + | ||
98 | 77 | + # If the remote_sg_id is removed, the tuple (sg_id, remote_sg_id, | ||
99 | 78 | + # direction, ethertype) no longer exists; the conjunction IDs assigned | ||
100 | 79 | + # to this tuple should be removed too. | ||
101 | 80 | + for k in list(self.id_map_group.keys()): | ||
102 | 81 | + if sg_id in k[0:2]: | ||
103 | 82 | + conj_id_groups = self.id_map_group.pop(k, []) | ||
104 | 83 | + for conj_id in conj_id_groups: | ||
105 | 84 | + result.add((k[1], conj_id)) | ||
106 | 85 | |||
107 | 86 | return result | ||
108 | 87 | |||
109 | 88 | @@ -346,12 +359,22 @@ | ||
110 | 89 | return addr_to_conj | ||
111 | 90 | |||
112 | 91 | def _update_flows_for_vlan_subr(self, direction, ethertype, vlan_tag, | ||
113 | 92 | - flow_state, addr_to_conj): | ||
114 | 93 | + flow_state, addr_to_conj, | ||
115 | 94 | + conj_id_to_remove): | ||
116 | 95 | """Do the actual flow updates for given direction and ethertype.""" | ||
117 | 96 | - current_ips = set(flow_state.keys()) | ||
118 | 97 | - self.driver.delete_flows_for_ip_addresses( | ||
119 | 98 | - current_ips - set(addr_to_conj.keys()), | ||
120 | 99 | - direction, ethertype, vlan_tag) | ||
121 | 100 | + conj_id_to_remove = conj_id_to_remove or [] | ||
122 | 101 | + # Delete any current flow related to any deleted IP address, before | ||
123 | 102 | + # creating the flows for the current IPs. | ||
124 | 103 | + self.driver.delete_flows_for_flow_state( | ||
125 | 104 | + flow_state, addr_to_conj, direction, ethertype, vlan_tag) | ||
126 | 105 | + for conj_id_set in conj_id_to_remove: | ||
127 | 106 | + # Remove any remaining flow with remote SG ID conj_id_to_remove | ||
128 | 107 | + for current_ip, conj_ids in flow_state.items(): | ||
129 | 108 | + conj_ids_to_remove = conj_id_set & set(conj_ids) | ||
130 | 109 | + self.driver.delete_flow_for_ip( | ||
131 | 110 | + current_ip, direction, ethertype, vlan_tag, | ||
132 | 111 | + conj_ids_to_remove) | ||
133 | 112 | + | ||
134 | 113 | for addr, conj_ids in addr_to_conj.items(): | ||
135 | 114 | conj_ids.sort() | ||
136 | 115 | if flow_state.get(addr) == conj_ids: | ||
137 | 116 | @@ -360,7 +383,7 @@ | ||
138 | 117 | addr, direction, ethertype, vlan_tag, conj_ids): | ||
139 | 118 | self.driver._add_flow(**flow) | ||
140 | 119 | |||
141 | 120 | - def update_flows_for_vlan(self, vlan_tag): | ||
142 | 121 | + def update_flows_for_vlan(self, vlan_tag, conj_id_to_remove=None): | ||
143 | 122 | """Install action=conjunction(conj_id, 1/2) flows, | ||
144 | 123 | which depend on IP addresses of remote_group_id. | ||
145 | 124 | """ | ||
146 | 125 | @@ -372,7 +395,7 @@ | ||
147 | 126 | ethertype, sg_conj_id_map) | ||
148 | 127 | self._update_flows_for_vlan_subr(direction, ethertype, vlan_tag, | ||
149 | 128 | self.flow_state[vlan_tag][(direction, ethertype)], | ||
150 | 129 | - addr_to_conj) | ||
151 | 130 | + addr_to_conj, conj_id_to_remove) | ||
152 | 131 | self.flow_state[vlan_tag][(direction, ethertype)] = addr_to_conj | ||
153 | 132 | |||
154 | 133 | def add(self, vlan_tag, sg_id, remote_sg_id, direction, ethertype, | ||
155 | 134 | @@ -393,32 +416,43 @@ | ||
156 | 135 | collections.defaultdict(set)) | ||
157 | 136 | self.conj_ids[vlan_tag][(direction, ethertype)][remote_sg_id].add( | ||
158 | 137 | conj_id) | ||
159 | 138 | + | ||
160 | 139 | + conj_id_tuple = (sg_id, remote_sg_id, direction, ethertype) | ||
161 | 140 | + self.conj_id_map.id_map_group[conj_id_tuple].add(conj_id) | ||
162 | 141 | return conj_id | ||
163 | 142 | |||
164 | 143 | def sg_removed(self, sg_id): | ||
165 | 144 | """Handle SG removal events. | ||
166 | 145 | |||
167 | 146 | - Free all conj_ids associated with the sg_id and clean up | ||
168 | 147 | + Free all conj_ids associated with the sg_id removed and clean up | ||
169 | 148 | obsolete entries from the self.conj_ids map. Unlike the add | ||
170 | 149 | method, it also updates flows. | ||
171 | 150 | + If a SG is removed, both sg_id and remote_sg_id should be removed from | ||
172 | 151 | + the "vlan_conj_id_map". | ||
173 | 152 | """ | ||
174 | 153 | - id_list = self.conj_id_map.delete_sg(sg_id) | ||
175 | 154 | + id_set = self.conj_id_map.delete_sg(sg_id) | ||
176 | 155 | unused_dict = collections.defaultdict(set) | ||
177 | 156 | - for remote_sg_id, conj_id in id_list: | ||
178 | 157 | + for remote_sg_id, conj_id in id_set: | ||
179 | 158 | unused_dict[remote_sg_id].add(conj_id) | ||
180 | 159 | |||
181 | 160 | for vlan_tag, vlan_conj_id_map in self.conj_ids.items(): | ||
182 | 161 | update = False | ||
183 | 162 | + conj_id_to_remove = [] | ||
184 | 163 | for sg_conj_id_map in vlan_conj_id_map.values(): | ||
185 | 164 | for remote_sg_id, unused in unused_dict.items(): | ||
186 | 165 | if (remote_sg_id in sg_conj_id_map and | ||
187 | 166 | sg_conj_id_map[remote_sg_id] & unused): | ||
188 | 167 | + if remote_sg_id == sg_id: | ||
189 | 168 | + conj_id_to_remove.append( | ||
190 | 169 | + sg_conj_id_map[remote_sg_id] & unused) | ||
191 | 170 | sg_conj_id_map[remote_sg_id] -= unused | ||
192 | 171 | if not sg_conj_id_map[remote_sg_id]: | ||
193 | 172 | del sg_conj_id_map[remote_sg_id] | ||
194 | 173 | update = True | ||
195 | 174 | + | ||
196 | 175 | if update: | ||
197 | 176 | - self.update_flows_for_vlan(vlan_tag) | ||
198 | 177 | + self.update_flows_for_vlan(vlan_tag, | ||
199 | 178 | + conj_id_to_remove=conj_id_to_remove) | ||
200 | 179 | |||
201 | 180 | |||
202 | 181 | class OVSFirewallDriver(firewall.FirewallDriver): | ||
203 | 182 | @@ -499,7 +533,8 @@ | ||
204 | 183 | |||
205 | 184 | def _delete_flows(self, **kwargs): | ||
206 | 185 | create_reg_numbers(kwargs) | ||
207 | 186 | - if self._deferred: | ||
208 | 187 | + deferred = kwargs.pop('deferred', self._deferred) | ||
209 | 188 | + if deferred: | ||
210 | 189 | self.int_br.delete_flows(**kwargs) | ||
211 | 190 | else: | ||
212 | 191 | self.int_br.br.delete_flows(**kwargs) | ||
213 | 192 | @@ -1416,20 +1451,31 @@ | ||
214 | 193 | in_port=port.ofport) | ||
215 | 194 | self._delete_flows(reg_port=port.ofport) | ||
216 | 195 | |||
217 | 196 | - def delete_flows_for_ip_addresses( | ||
218 | 197 | - self, ip_addresses, direction, ethertype, vlan_tag): | ||
219 | 198 | + def delete_flows_for_flow_state( | ||
220 | 199 | + self, flow_state, addr_to_conj, direction, ethertype, vlan_tag): | ||
221 | 200 | + # Remove rules for deleted IPs and action=conjunction(conj_id, 1/2) | ||
222 | 201 | + removed_ips = set(flow_state.keys()) - set(addr_to_conj.keys()) | ||
223 | 202 | + for removed_ip in removed_ips: | ||
224 | 203 | + conj_ids = flow_state[removed_ip] | ||
225 | 204 | + self.delete_flow_for_ip(removed_ip, direction, ethertype, vlan_tag, | ||
226 | 205 | + conj_ids) | ||
227 | 206 | + | ||
228 | 207 | if not cfg.CONF.AGENT.explicitly_egress_direct: | ||
229 | 208 | return | ||
230 | 209 | |||
231 | 210 | - for ip_addr in ip_addresses: | ||
232 | 211 | + for ip_addr in removed_ips: | ||
233 | 212 | # Generate deletion template with bogus conj_id. | ||
234 | 213 | - flows = rules.create_flows_for_ip_address( | ||
235 | 214 | - ip_addr, direction, ethertype, vlan_tag, [0]) | ||
236 | 215 | - for f in flows: | ||
237 | 216 | - # The following del statements are partly for | ||
238 | 217 | - # complying the OpenFlow spec. It forbids the use of | ||
239 | 218 | - # these field in non-strict delete flow messages, and | ||
240 | 219 | - # the actions field is bogus anyway. | ||
241 | 220 | - del f['actions'] | ||
242 | 221 | - del f['priority'] | ||
243 | 222 | - self._delete_flows(**f) | ||
244 | 223 | + self.delete_flow_for_ip(ip_addr, direction, ethertype, vlan_tag, | ||
245 | 224 | + [0]) | ||
246 | 225 | + | ||
247 | 226 | + def delete_flow_for_ip(self, ip_address, direction, ethertype, | ||
248 | 227 | + vlan_tag, conj_ids): | ||
249 | 228 | + for flow in rules.create_flows_for_ip_address( | ||
250 | 229 | + ip_address, direction, ethertype, vlan_tag, conj_ids): | ||
251 | 230 | + # The following del statements are partly for | ||
252 | 231 | + # complying the OpenFlow spec. It forbids the use of | ||
253 | 232 | + # these field in non-strict delete flow messages, and | ||
254 | 233 | + # the actions field is bogus anyway. | ||
255 | 234 | + del flow['actions'] | ||
256 | 235 | + del flow['priority'] | ||
257 | 236 | + self._delete_flows(deferred=False, **flow) | ||
258 | 237 | diff --git a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py b/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py | ||
259 | 238 | index 6aeb7a4..c1aed92 100644 | ||
260 | 239 | --- a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py | ||
261 | 240 | +++ b/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py | ||
262 | 241 | @@ -282,23 +282,50 @@ | ||
263 | 242 | constants.IPv6) | ||
264 | 243 | |||
265 | 244 | def test_delete_sg(self): | ||
266 | 245 | - test_data = [('sg1', 'sg1'), ('sg1', 'sg2')] | ||
267 | 246 | + test_data = [ | ||
268 | 247 | + # conj_id: 8 | ||
269 | 248 | + ('sg1', 'sg1', constants.INGRESS_DIRECTION, constants.IPv6, 0), | ||
270 | 249 | + # conj_id: 10 | ||
271 | 250 | + ('sg1', 'sg1', constants.INGRESS_DIRECTION, constants.IPv6, 1), | ||
272 | 251 | + # conj_id: 12 | ||
273 | 252 | + ('sg1', 'sg1', constants.INGRESS_DIRECTION, constants.IPv6, 2), | ||
274 | 253 | + # conj_id: 16 | ||
275 | 254 | + ('sg2', 'sg1', constants.EGRESS_DIRECTION, constants.IPv6, 0), | ||
276 | 255 | + # conj_id: 24 | ||
277 | 256 | + ('sg1', 'sg3', constants.INGRESS_DIRECTION, constants.IPv6, 0), | ||
278 | 257 | + # conj_id: 36 (and 32 without priority offset, stored in id_map) | ||
279 | 258 | + ('sg3', 'sg4', constants.INGRESS_DIRECTION, constants.IPv4, 2), | ||
280 | 259 | + # conj_id: 40 | ||
281 | 260 | + ('sg5', 'sg4', constants.EGRESS_DIRECTION, constants.IPv4, 0), | ||
282 | 261 | + ] | ||
283 | 262 | |||
284 | 263 | ids = [] | ||
285 | 264 | - for sg_id, remote_sg_id in test_data: | ||
286 | 265 | - ids.append(self.conj_id_map.get_conj_id( | ||
287 | 266 | - sg_id, remote_sg_id, | ||
288 | 267 | - firewall.INGRESS_DIRECTION, constants.IPv6)) | ||
289 | 268 | + conj_id_segment = set([]) # see ConjIPFlowManager.get_conj_id | ||
290 | 269 | + # This is similar to ConjIPFlowManager.add method | ||
291 | 270 | + for sg_id, rsg_id, direction, ip_version, prio_offset in test_data: | ||
292 | 271 | + conj_id_tuple = (sg_id, rsg_id, direction, ip_version) | ||
293 | 272 | + conj_id = self.conj_id_map.get_conj_id(*conj_id_tuple) | ||
294 | 273 | + conj_id_segment.add(conj_id) | ||
295 | 274 | + conj_id_plus_prio = conj_id + prio_offset * 2 | ||
296 | 275 | + self.conj_id_map.id_map_group[conj_id_tuple].add(conj_id_plus_prio) | ||
297 | 276 | + ids.append(conj_id_plus_prio) | ||
298 | 277 | |||
299 | 278 | result = self.conj_id_map.delete_sg('sg1') | ||
300 | 279 | - self.assertIn(('sg1', ids[0]), result) | ||
301 | 280 | - self.assertIn(('sg2', ids[1]), result) | ||
302 | 281 | - self.assertFalse(self.conj_id_map.id_map) | ||
303 | 282 | + self.assertEqual( | ||
304 | 283 | + {('sg3', 24), ('sg1', 12), ('sg1', 16), ('sg1', 8), ('sg1', 10)}, | ||
305 | 284 | + result) | ||
306 | 285 | + result = self.conj_id_map.delete_sg('sg3') | ||
307 | 286 | + self.assertEqual({('sg4', 32), ('sg4', 36)}, result) | ||
308 | 287 | + result = self.conj_id_map.delete_sg('sg4') | ||
309 | 288 | + self.assertEqual({('sg4', 40)}, result) | ||
310 | 289 | + self.assertEqual({}, self.conj_id_map.id_map) | ||
311 | 290 | + self.assertEqual({}, self.conj_id_map.id_map_group) | ||
312 | 291 | |||
313 | 292 | - reallocated = self.conj_id_map.get_conj_id( | ||
314 | 293 | - 'sg-foo', 'sg-foo', firewall.INGRESS_DIRECTION, | ||
315 | 294 | - constants.IPv6) | ||
316 | 295 | - self.assertIn(reallocated, ids) | ||
317 | 296 | + reallocated = set([]) | ||
318 | 297 | + for sg_id, rsg_id, direction, ip_version, _ in test_data: | ||
319 | 298 | + conj_id_tuple = (sg_id, rsg_id, direction, ip_version) | ||
320 | 299 | + reallocated.add(self.conj_id_map.get_conj_id(*conj_id_tuple)) | ||
321 | 300 | + self.assertEqual(reallocated, conj_id_segment) | ||
322 | 301 | |||
323 | 302 | |||
324 | 303 | class TestConjIPFlowManager(base.BaseTestCase): | ||
325 | 304 | @@ -363,7 +390,7 @@ | ||
326 | 305 | dl_type=2048, nw_src='10.22.3.4/32', priority=73, | ||
327 | 306 | reg_net=self.vlan_tag, table=82)]) | ||
328 | 307 | |||
329 | 308 | - def test_sg_removed(self): | ||
330 | 309 | + def _sg_removed(self, sg_name): | ||
331 | 310 | with mock.patch.object(self.manager.conj_id_map, | ||
332 | 311 | 'get_conj_id') as get_id_mock, \ | ||
333 | 312 | mock.patch.object(self.manager.conj_id_map, | ||
334 | 313 | @@ -376,11 +403,26 @@ | ||
335 | 314 | firewall.INGRESS_DIRECTION, constants.IPv4)] = { | ||
336 | 315 | '10.22.3.4': [self.conj_id]} | ||
337 | 316 | |||
338 | 317 | - self.manager.sg_removed('sg') | ||
339 | 318 | + self.manager.sg_removed(sg_name) | ||
340 | 319 | + | ||
341 | 320 | + def test_sg_removed(self): | ||
342 | 321 | + self._sg_removed('sg') | ||
343 | 322 | self.driver._add_flow.assert_not_called() | ||
344 | 323 | - self.driver.delete_flows_for_ip_addresses.assert_called_once_with( | ||
345 | 324 | - {'10.22.3.4'}, firewall.INGRESS_DIRECTION, constants.IPv4, | ||
346 | 325 | - self.vlan_tag) | ||
347 | 326 | + self.driver.delete_flows_for_flow_state.assert_called_once_with( | ||
348 | 327 | + {'10.22.3.4': [self.conj_id]}, {}, | ||
349 | 328 | + constants.INGRESS_DIRECTION, constants.IPv4, self.vlan_tag) | ||
350 | 329 | + self.driver.delete_flow_for_ip.assert_not_called() | ||
351 | 330 | + | ||
352 | 331 | + def test_remote_sg_removed(self): | ||
353 | 332 | + self._sg_removed('remote_id') | ||
354 | 333 | + self.driver._add_flow.assert_not_called() | ||
355 | 334 | + self.driver.delete_flows_for_flow_state.assert_called_once_with( | ||
356 | 335 | + {'10.22.3.4': [self.conj_id]}, {}, | ||
357 | 336 | + constants.INGRESS_DIRECTION, constants.IPv4, self.vlan_tag) | ||
358 | 337 | + # "conj_id_to_remove" is populated with the remote_sg conj_id assigned, | ||
359 | 338 | + # "_update_flows_for_vlan_subr" will call "delete_flow_for_ip". | ||
360 | 339 | + self.driver.delete_flow_for_ip.assert_called_once_with( | ||
361 | 340 | + '10.22.3.4', 'ingress', 'IPv4', 100, {self.conj_id}) | ||
362 | 341 | |||
363 | 342 | |||
364 | 343 | class FakeOVSPort(object): | ||
365 | 344 | @@ -401,7 +443,6 @@ | ||
366 | 345 | self.fake_ovs_port = FakeOVSPort('port', 1, '00:00:00:00:00:00') | ||
367 | 346 | self.mock_bridge.br.get_vif_port_by_id.return_value = \ | ||
368 | 347 | self.fake_ovs_port | ||
369 | 348 | - cfg.CONF.set_override('explicitly_egress_direct', True, 'AGENT') | ||
370 | 349 | |||
371 | 350 | def _prepare_security_group(self): | ||
372 | 351 | security_group_rules = [ | ||
373 | 352 | @@ -925,6 +966,43 @@ | ||
374 | 353 | """Check that exception is not propagated outside.""" | ||
375 | 354 | self.firewall.remove_trusted_ports(['port_id']) | ||
376 | 355 | |||
377 | 356 | + def _test_delete_flows_for_flow_state(self, addr_to_conj, | ||
378 | 357 | + explicitly_egress_direct=True): | ||
379 | 358 | + direction = 'one_direction' | ||
380 | 359 | + ethertype = 'ethertype' | ||
381 | 360 | + vlan_tag = 'taaag' | ||
382 | 361 | + with mock.patch.object(self.firewall, 'delete_flow_for_ip') as \ | ||
383 | 362 | + mock_delete_flow_for_ip: | ||
384 | 363 | + flow_state = {'addr1': {8, 16, 24}, 'addr2': {32, 40}} | ||
385 | 364 | + cfg.CONF.set_override('explicitly_egress_direct', | ||
386 | 365 | + explicitly_egress_direct, 'AGENT') | ||
387 | 366 | + self.firewall.delete_flows_for_flow_state( | ||
388 | 367 | + flow_state, addr_to_conj, direction, ethertype, vlan_tag) | ||
389 | 368 | + calls = [] | ||
390 | 369 | + for removed_ip in set(flow_state.keys()) - set(addr_to_conj.keys()): | ||
391 | 370 | + calls.append(mock.call(removed_ip, direction, ethertype, vlan_tag, | ||
392 | 371 | + flow_state[removed_ip])) | ||
393 | 372 | + if explicitly_egress_direct: | ||
394 | 373 | + calls.append(mock.call(removed_ip, direction, ethertype, | ||
395 | 374 | + vlan_tag, [0])) | ||
396 | 375 | + mock_delete_flow_for_ip.assert_has_calls(calls) | ||
397 | 376 | + | ||
398 | 377 | + def test_delete_flows_for_flow_state_no_removed_ips_exp_egress(self): | ||
399 | 378 | + addr_to_conj = {'addr1': {8, 16, 24}, 'addr2': {32, 40}} | ||
400 | 379 | + self._test_delete_flows_for_flow_state(addr_to_conj) | ||
401 | 380 | + | ||
402 | 381 | + def test_delete_flows_for_flow_state_no_removed_ips_no_exp_egress(self): | ||
403 | 382 | + addr_to_conj = {'addr1': {8, 16, 24}, 'addr2': {32, 40}} | ||
404 | 383 | + self._test_delete_flows_for_flow_state(addr_to_conj, False) | ||
405 | 384 | + | ||
406 | 385 | + def test_delete_flows_for_flow_state_removed_ips_exp_egress(self): | ||
407 | 386 | + addr_to_conj = {'addr2': {32, 40}} | ||
408 | 387 | + self._test_delete_flows_for_flow_state(addr_to_conj) | ||
409 | 388 | + | ||
410 | 389 | + def test_delete_flows_for_flow_state_removed_ips_no_exp_egress(self): | ||
411 | 390 | + addr_to_conj = {'addr1': {8, 16, 24}} | ||
412 | 391 | + self._test_delete_flows_for_flow_state(addr_to_conj, False) | ||
413 | 392 | + | ||
414 | 393 | |||
415 | 394 | class TestCookieContext(base.BaseTestCase): | ||
416 | 395 | def setUp(self): | ||
417 | diff --git a/debian/patches/series b/debian/patches/series | |||
418 | index 96882c3..e65079e 100644 | |||
419 | --- a/debian/patches/series | |||
420 | +++ b/debian/patches/series | |||
421 | @@ -1,3 +1,4 @@ | |||
422 | 1 | skip-iptest.patch | 1 | skip-iptest.patch |
423 | 2 | flake8-legacy.patch | 2 | flake8-legacy.patch |
424 | 3 | Ensure-fip-ip-rules-deleted-when-fip-removed.patch | 3 | Ensure-fip-ip-rules-deleted-when-fip-removed.patch |
425 | 4 | lp1881157-remote-sg-is-left-behind.patch |
Seems fine on a quick glance. We'll need to get point releases uploaded for > queens before we can upload this one. Let's hold off on merging until then in case something else comes up ahead of this.