Skip to content

Commit e62c81a

Browse files
author
Ihar Hrachyshka
committed
ovn: first tear down old metadata namespaces, then deploy new
While the reverse order may work, it's considered invalid by OVN and not guaranteed to work properly since OVN may not necessarily know which of two ports is the one to configure. This configuration also triggered a bug in OVN where tearing down a port after deploying a new one resulted in removing flows that serve the port. There is a patch up for review for OVN [1] to better handle multiple assignment of the same port, but it doesn't make the setup any more valid. [1] http://patchwork.ozlabs.org/project/ovn/patch/[email protected]/ Closes-Bug: #1997092 Change-Id: Ic7dbc4e8b00423e58f69646a9e3cedc6f72d6c63 (cherry picked from commit 3093aaa)
1 parent 9a83017 commit e62c81a

File tree

2 files changed

+51
-64
lines changed

2 files changed

+51
-64
lines changed

neutron/agent/ovn/metadata/agent.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,12 @@ def _get_ovn_bridge(self):
319319
"br-int instead.")
320320
return 'br-int'
321321

322+
def get_networks(self):
323+
ports = self.sb_idl.get_ports_on_chassis(self.chassis)
324+
return {(str(p.datapath.uuid),
325+
ovn_utils.get_network_name_from_datapath(p.datapath))
326+
for p in self._vif_ports(ports)}
327+
322328
@_sync_lock
323329
def sync(self):
324330
"""Agent sync.
@@ -327,16 +333,26 @@ def sync(self):
327333
chassis are serving metadata. Also, it will tear down those namespaces
328334
which were serving metadata but are no longer needed.
329335
"""
330-
metadata_namespaces = self.ensure_all_networks_provisioned()
336+
337+
# first, clean up namespaces that should no longer deploy
331338
system_namespaces = tuple(
332339
ns.decode('utf-8') if isinstance(ns, bytes) else ns
333340
for ns in ip_lib.list_network_namespaces())
341+
nets = self.get_networks()
342+
metadata_namespaces = [
343+
self._get_namespace_name(net[1])
344+
for net in nets
345+
]
334346
unused_namespaces = [ns for ns in system_namespaces if
335347
ns.startswith(NS_PREFIX) and
336348
ns not in metadata_namespaces]
337349
for ns in unused_namespaces:
338350
self.teardown_datapath(self._get_datapath_name(ns))
339351

352+
# now that all obsolete namespaces are cleaned up, deploy required
353+
# networks
354+
self.ensure_all_networks_provisioned(nets)
355+
340356
@staticmethod
341357
def _get_veth_name(datapath):
342358
return ['{}{}{}'.format(n_const.TAP_DEVICE_PREFIX,
@@ -428,8 +444,6 @@ def provision_datapath(self, datapath, net_name):
428444
and assign the IP addresses to the interface corresponding to the
429445
metadata port of the network. It will also remove existing IP
430446
addresses that are no longer needed.
431-
432-
:return: The metadata namespace name of this datapath
433447
"""
434448
LOG.debug("Provisioning metadata for network %s", net_name)
435449
port = self.sb_idl.get_metadata_port_network(datapath)
@@ -537,28 +551,13 @@ def provision_datapath(self, datapath, net_name):
537551
self.conf, bind_address=n_const.METADATA_V4_IP,
538552
network_id=net_name)
539553

540-
return namespace
554+
def ensure_all_networks_provisioned(self, nets):
555+
"""Ensure that all requested datapaths are provisioned.
541556
542-
def ensure_all_networks_provisioned(self):
543-
"""Ensure that all datapaths are provisioned.
544-
545-
This function will make sure that all datapaths with ports bound to
546-
our chassis have its namespace, VETH pair and OVS port created and
547-
metadata proxy is up and running.
548-
549-
:return: A list with the namespaces that are currently serving
550-
metadata
557+
This function will make sure that requested datapaths have their
558+
namespaces, VETH pair and OVS ports created and metadata proxies are up
559+
and running.
551560
"""
552-
# Retrieve all VIF ports in our Chassis
553-
ports = self.sb_idl.get_ports_on_chassis(self.chassis)
554-
nets = {(str(p.datapath.uuid),
555-
ovn_utils.get_network_name_from_datapath(p.datapath))
556-
for p in self._vif_ports(ports)}
557-
namespaces = []
558561
# Make sure that all those datapaths are serving metadata
559562
for datapath, net_name in nets:
560-
netns = self.provision_datapath(datapath, net_name)
561-
if netns:
562-
namespaces.append(netns)
563-
564-
return namespaces
563+
self.provision_datapath(datapath, net_name)

neutron/tests/unit/agent/ovn/metadata/test_agent.py

Lines changed: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,29 @@ def setUp(self):
7171
self.agent.chassis = 'chassis'
7272
self.agent.ovn_bridge = 'br-int'
7373

74+
self.ports = []
75+
for i in range(0, 3):
76+
self.ports.append(makePort(datapath=DatapathInfo(uuid=str(i),
77+
external_ids={'name': 'neutron-%d' % i})))
78+
self.agent.sb_idl.get_ports_on_chassis.return_value = self.ports
79+
7480
def test_sync(self):
81+
7582
with mock.patch.object(
7683
self.agent, 'ensure_all_networks_provisioned') as enp,\
7784
mock.patch.object(
7885
ip_lib, 'list_network_namespaces') as lnn,\
7986
mock.patch.object(
8087
self.agent, 'teardown_datapath') as tdp:
81-
enp.return_value = ['ovnmeta-1', 'ovnmeta-2']
8288
lnn.return_value = ['ovnmeta-1', 'ovnmeta-2']
8389

8490
self.agent.sync()
8591

86-
enp.assert_called_once_with()
92+
enp.assert_called_once_with({
93+
(p.datapath.uuid, p.datapath.uuid)
94+
for p in self.ports
95+
})
96+
8797
lnn.assert_called_once_with()
8898
tdp.assert_not_called()
8999

@@ -95,18 +105,20 @@ def test_sync_teardown_namespace(self):
95105
ip_lib, 'list_network_namespaces') as lnn,\
96106
mock.patch.object(
97107
self.agent, 'teardown_datapath') as tdp:
98-
enp.return_value = ['ovnmeta-1', 'ovnmeta-2']
99108
lnn.return_value = ['ovnmeta-1', 'ovnmeta-2', 'ovnmeta-3',
100109
'ns1', 'ns2']
101110

102111
self.agent.sync()
103112

104-
enp.assert_called_once_with()
113+
enp.assert_called_once_with({
114+
(p.datapath.uuid, p.datapath.uuid)
115+
for p in self.ports
116+
})
105117
lnn.assert_called_once_with()
106118
tdp.assert_called_once_with('3')
107119

108-
def test_ensure_all_networks_provisioned(self):
109-
"""Test networks are provisioned.
120+
def test_get_networks(self):
121+
"""Test which networks are provisioned.
110122
111123
This test simulates that this chassis has the following ports:
112124
* datapath '0': 1 port
@@ -115,61 +127,37 @@ def test_ensure_all_networks_provisioned(self):
115127
* datapath '3': 1 port with type 'external'
116128
* datapath '5': 1 port with type 'unknown'
117129
118-
It is expected that only datapaths '0', '1' and '2' are provisioned
119-
once.
130+
It is expected that only datapaths '0', '1' and '2' are scheduled for
131+
provisioning.
120132
"""
121133

122-
ports = []
123-
for i in range(0, 3):
124-
ports.append(makePort(datapath=DatapathInfo(uuid=str(i),
125-
external_ids={'name': 'neutron-%d' % i})))
126-
ports.append(makePort(datapath=DatapathInfo(uuid='1',
134+
self.ports.append(makePort(datapath=DatapathInfo(uuid='1',
127135
external_ids={'name': 'neutron-1'})))
128-
ports.append(makePort(datapath=DatapathInfo(uuid='3',
136+
self.ports.append(makePort(datapath=DatapathInfo(uuid='3',
129137
external_ids={'name': 'neutron-3'}), type='external'))
130-
ports.append(makePort(datapath=DatapathInfo(uuid='5',
138+
self.ports.append(makePort(datapath=DatapathInfo(uuid='5',
131139
external_ids={'name': 'neutron-5'}), type='unknown'))
132140

133-
with mock.patch.object(self.agent, 'provision_datapath',
134-
return_value=None) as pdp,\
135-
mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis',
136-
return_value=ports):
137-
self.agent.ensure_all_networks_provisioned()
138-
139-
expected_calls = [mock.call(str(i), str(i)) for i in range(0, 4)]
140-
self.assertEqual(sorted(expected_calls),
141-
sorted(pdp.call_args_list))
141+
expected_networks = {(str(i), str(i)) for i in range(0, 4)}
142+
self.assertEqual(expected_networks, self.agent.get_networks())
142143

143144
def test_update_datapath_provision(self):
144-
ports = []
145-
for i in range(0, 3):
146-
ports.append(makePort(datapath=DatapathInfo(uuid=str(i),
147-
external_ids={'name': 'neutron-%d' % i})))
148-
ports.append(makePort(datapath=DatapathInfo(uuid='3',
145+
self.ports.append(makePort(datapath=DatapathInfo(uuid='3',
149146
external_ids={'name': 'neutron-3'}), type='external'))
150147

151148
with mock.patch.object(self.agent, 'provision_datapath',
152149
return_value=None) as pdp,\
153-
mock.patch.object(self.agent, 'teardown_datapath') as tdp,\
154-
mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis',
155-
return_value=ports):
150+
mock.patch.object(self.agent, 'teardown_datapath') as tdp:
156151
self.agent.update_datapath('1', 'a')
157152
self.agent.update_datapath('3', 'b')
158153
expected_calls = [mock.call('1', 'a'), mock.call('3', 'b')]
159154
pdp.assert_has_calls(expected_calls)
160155
tdp.assert_not_called()
161156

162157
def test_update_datapath_teardown(self):
163-
ports = []
164-
for i in range(0, 3):
165-
ports.append(makePort(datapath=DatapathInfo(uuid=str(i),
166-
external_ids={'name': 'neutron-%d' % i})))
167-
168158
with mock.patch.object(self.agent, 'provision_datapath',
169159
return_value=None) as pdp,\
170-
mock.patch.object(self.agent, 'teardown_datapath') as tdp,\
171-
mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis',
172-
return_value=ports):
160+
mock.patch.object(self.agent, 'teardown_datapath') as tdp:
173161
self.agent.update_datapath('5', 'a')
174162
tdp.assert_called_once_with('5', 'a')
175163
pdp.assert_not_called()

0 commit comments

Comments
 (0)