Browse Source

always create ovs port during plug

- This change modifies the ovs plugin to always
  create the ovs interface in the ovs db.
- This change enables the neutron l2 agent to configure
  the ovs interface by assigning a vlan tag and
  installing openflow rules as appropriate.
- This change will reduce the live migration
  time for kernel ovs ports with hybrid plug false
  by creating the ovs port as part of plug before
  the migration starts.
- This change adds the privsep decorator
  to delete_net_dev to account for it new usage
  via _unplug_vif_generic and address bug #1801072

Change-Id: Iaf15fa7a678ec2624f7c12f634269c465fbad930
Partial-Bug: #1734320
Closes-Bug: #1801072
tags/1.13.0
Sean Mooney 8 months ago
parent
commit
165ed32591

+ 24
- 0
releasenotes/notes/always-plug-vifs-for-ovs-1d033fc49a9c6c4e.yaml View File

@@ -0,0 +1,24 @@
1
+---
2
+features:
3
+  - |
4
+    In this release the OVS plugin was extended to always plug VIFs even when
5
+    libvirt could plug the vif. This will enable faster migration leveraging
6
+    the multiple port bindings work completed in the Rocky release.
7
+security:
8
+  - |
9
+    In this release an edgecase where libvirt plugged the VIF instead of os-vif
10
+    was addressed. Previously if ``ovs_hybrid_plug`` was set to ``False`` in
11
+    the port binding details, os-vif would only ensure the ovs bridge existed
12
+    and the plugging would be done by libvirt. As a result during live
13
+    migration, there was a short interval where a guest could receive tagged
14
+    broadcast, multicast, or flooded traffic to/from another tenant.
15
+    This vulnerability is described in `bug 1734320`_. By ensuring that
16
+    os-vif always creates the OVS port as part of vif plugging we enable
17
+    neutron to isolate the port prior to nova resuming the VM on the
18
+    destination node. Note that as Nova cannot rely on Neutron to send
19
+    ``network-vif-plugged`` events on completion of wiring up an interface
20
+    it cannot wait to receive a notification before proceeding with the
21
+    migration. As a result this is a partial mitigation and additional changes
22
+    will be required to fully address this bug.
23
+
24
+    .. _bug 1734320: https://bugs.launchpad.net/neutron/+bug/1734320

+ 3
- 1
vif_plug_ovs/linux_net.py View File

@@ -66,6 +66,7 @@ def interface_in_bridge(bridge, device):
66 66
                           {'bridge': bridge, 'device': device})
67 67
 
68 68
 
69
+@privsep.vif_plug.entrypoint
69 70
 def delete_net_dev(dev):
70 71
     """Delete a network device only if it exists."""
71 72
     if ip_lib.exists(dev):
@@ -139,7 +140,8 @@ def add_bridge_port(bridge, dev):
139 140
 @privsep.vif_plug.entrypoint
140 141
 def set_device_mtu(dev, mtu):
141 142
     """Set the device MTU."""
142
-    ip_lib.set(dev, mtu=mtu, check_exit_code=[0, 2, 254])
143
+    if ip_lib.exists(dev):
144
+        ip_lib.set(dev, mtu=mtu, check_exit_code=[0, 2, 254])
143 145
 
144 146
 
145 147
 @privsep.vif_plug.entrypoint

+ 14
- 3
vif_plug_ovs/ovs.py View File

@@ -197,6 +197,12 @@ class OvsPlugin(plugin.PluginBase):
197 197
                                          self._get_vif_datapath_type(vif))
198 198
             self._create_vif_port(vif, vif.id, instance_info)
199 199
 
200
+    def _plug_vif_generic(self, vif, instance_info):
201
+        """Create a per-VIF OVS port."""
202
+        self.ovsdb.ensure_ovs_bridge(vif.network.bridge,
203
+                                     self._get_vif_datapath_type(vif))
204
+        self._create_vif_port(vif, vif.vif_name, instance_info)
205
+
200 206
     def _plug_vf_passthrough(self, vif, instance_info):
201 207
         self.ovsdb.ensure_ovs_bridge(
202 208
             vif.network.bridge, constants.OVS_DATAPATH_SYSTEM)
@@ -218,8 +224,7 @@ class OvsPlugin(plugin.PluginBase):
218 224
 
219 225
         if isinstance(vif, objects.vif.VIFOpenVSwitch):
220 226
             if sys.platform != constants.PLATFORM_WIN32:
221
-                self.ovsdb.ensure_ovs_bridge(vif.network.bridge,
222
-                    self._get_vif_datapath_type(vif))
227
+                self._plug_vif_generic(vif, instance_info)
223 228
             else:
224 229
                 self._plug_vif_windows(vif, instance_info)
225 230
         elif isinstance(vif, objects.vif.VIFBridge):
@@ -255,6 +260,10 @@ class OvsPlugin(plugin.PluginBase):
255 260
         """Remove port from OVS."""
256 261
         self.ovsdb.delete_ovs_vif_port(vif.network.bridge, vif.id)
257 262
 
263
+    def _unplug_vif_generic(self, vif, instance_info):
264
+        """Remove port from OVS."""
265
+        self.ovsdb.delete_ovs_vif_port(vif.network.bridge, vif.vif_name)
266
+
258 267
     def _unplug_vf_passthrough(self, vif, instance_info):
259 268
         """Remove port from OVS."""
260 269
         pci_slot = vif.dev_address
@@ -278,7 +287,9 @@ class OvsPlugin(plugin.PluginBase):
278 287
                 profile=vif.port_profile.__class__.__name__)
279 288
 
280 289
         if isinstance(vif, objects.vif.VIFOpenVSwitch):
281
-            if sys.platform == constants.PLATFORM_WIN32:
290
+            if sys.platform != constants.PLATFORM_WIN32:
291
+                self._unplug_vif_generic(vif, instance_info)
292
+            else:
282 293
                 self._unplug_vif_windows(vif, instance_info)
283 294
         elif isinstance(vif, objects.vif.VIFBridge):
284 295
             if sys.platform != constants.PLATFORM_WIN32:

+ 26
- 13
vif_plug_ovs/tests/unit/test_plugin.py View File

@@ -69,7 +69,7 @@ class PluginTest(testtools.TestCase):
69 69
             id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
70 70
             address='ca:fe:de:ad:be:ef',
71 71
             network=self.network_ovs,
72
-            dev_name='tap-xxx-yyy-zzz',
72
+            vif_name='tap-xxx-yyy-zzz',
73 73
             bridge_name="qbrvif-xxx-yyy",
74 74
             port_profile=self.profile_ovs_no_datatype)
75 75
 
@@ -77,7 +77,7 @@ class PluginTest(testtools.TestCase):
77 77
             id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
78 78
             address='ca:fe:de:ad:be:ef',
79 79
             network=self.network_ovs,
80
-            dev_name='tap-xxx-yyy-zzz',
80
+            vif_name='tap-xxx-yyy-zzz',
81 81
             port_profile=self.profile_ovs)
82 82
 
83 83
         self.vif_vhostuser = objects.vif.VIFVHostUser(
@@ -147,16 +147,22 @@ class PluginTest(testtools.TestCase):
147 147
             interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE)
148 148
 
149 149
     @mock.patch.object(ovs, 'sys')
150
-    @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge')
151
-    def test_plug_ovs(self, ensure_ovs_bridge, mock_sys):
150
+    @mock.patch.object(ovs.OvsPlugin, '_plug_vif_generic')
151
+    def test_plug_ovs(self, plug_vif_generic, mock_sys):
152 152
         mock_sys.platform = 'linux'
153
-        plug_bridge_mock = mock.Mock()
154 153
         plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
155
-        plugin._plug_bridge = plug_bridge_mock
156 154
         plugin.plug(self.vif_ovs, self.instance)
157
-        dp_type = ovs.OvsPlugin._get_vif_datapath_type(self.vif_ovs)
158
-        ensure_ovs_bridge.assert_called_once_with(self.vif_ovs.network.bridge,
159
-                                                  dp_type)
155
+        plug_vif_generic.assert_called_once_with(self.vif_ovs,
156
+                                                 self.instance)
157
+
158
+    @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge')
159
+    @mock.patch.object(ovs.OvsPlugin, "_create_vif_port")
160
+    def test_plug_vif_generic(self, create_port, ensure_bridge):
161
+        plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
162
+        plugin._plug_vif_generic(self.vif_ovs, self.instance)
163
+        ensure_bridge.assert_called_once()
164
+        create_port.assert_called_once_with(self.vif_ovs,
165
+            self.vif_ovs.vif_name, self.instance)
160 166
 
161 167
     @mock.patch.object(linux_net, 'set_interface_state')
162 168
     @mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge')
@@ -250,12 +256,19 @@ class PluginTest(testtools.TestCase):
250 256
     def test_plug_ovs_bridge_windows(self):
251 257
         self._check_plug_ovs_windows(self.vif_ovs_hybrid)
252 258
 
253
-    def test_unplug_ovs(self):
254
-        unplug_bridge_mock = mock.Mock()
259
+    @mock.patch.object(ovs, 'sys')
260
+    @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic')
261
+    def test_unplug_ovs(self, unplug, mock_sys):
262
+        mock_sys.platform = 'linux'
255 263
         plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
256
-        plugin._unplug_bridge = unplug_bridge_mock
257 264
         plugin.unplug(self.vif_ovs, self.instance)
258
-        unplug_bridge_mock.assert_not_called()
265
+        unplug.assert_called_once_with(self.vif_ovs, self.instance)
266
+
267
+    @mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic')
268
+    def test_unplug_vif_generic(self, delete_port):
269
+        plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
270
+        plugin._unplug_vif_generic(self.vif_ovs, self.instance)
271
+        delete_port.assert_called_once()
259 272
 
260 273
     @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port')
261 274
     @mock.patch.object(linux_net, 'delete_bridge')

Loading…
Cancel
Save