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