os-vif 4.2.1__py3-none-any.whl → 4.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of os-vif might be problematic. Click here for more details.

@@ -18,6 +18,7 @@ class IpCommand(metaclass=abc.ABCMeta):
18
18
  TYPE_VETH = 'veth'
19
19
  TYPE_VLAN = 'vlan'
20
20
  TYPE_BRIDGE = 'bridge'
21
+ TYPE_TUNTAP = 'tuntap'
21
22
 
22
23
  @abc.abstractmethod
23
24
  def set(self, device, check_exit_code=None, state=None, mtu=None,
@@ -37,11 +38,12 @@ class IpCommand(metaclass=abc.ABCMeta):
37
38
 
38
39
  @abc.abstractmethod
39
40
  def add(self, device, dev_type, check_exit_code=None, peer=None, link=None,
40
- vlan_id=None, ageing=None):
41
+ vlan_id=None, ageing=None, mode=None, multiqueue=False):
41
42
  """Method to add an interface.
42
43
 
43
44
  :param device: A network device (string)
44
- :param dev_type: String network device type (TYPE_VETH, TYPE_VLAN)
45
+ :param dev_type: String network device type (TYPE_VETH, TYPE_VLAN,
46
+ TYPE_BRIDGE, TYPE_TUNTAP)
45
47
  :param check_exit_code: List of integers of allowed execution exit
46
48
  codes
47
49
  :param peer: String peer name, for veth interfaces
@@ -50,6 +52,8 @@ class IpCommand(metaclass=abc.ABCMeta):
50
52
  :param vlan_id: Integer VLAN ID for VLAN devices
51
53
  :param ageing: integer value in seconds before learned
52
54
  mac addresses are forgotten.
55
+ :param mode: String mode for tuntap devices ('tap' or 'tun')
56
+ :param multiqueue: Boolean to enable multiqueue for tuntap devices
53
57
  :return: status of the command execution
54
58
  """
55
59
 
@@ -74,7 +74,7 @@ class PyRoute2(ip_command.IpCommand):
74
74
  return idx[0]
75
75
 
76
76
  def add(self, device, dev_type, check_exit_code=None, peer=None, link=None,
77
- vlan_id=None, ageing=None):
77
+ vlan_id=None, ageing=None, mode=None, multiqueue=False):
78
78
  check_exit_code = check_exit_code or []
79
79
  with iproute.IPRoute() as ip:
80
80
  args = {'ifname': device,
@@ -102,6 +102,15 @@ class PyRoute2(ip_command.IpCommand):
102
102
  # what policy to use and keep this code generic.
103
103
  if ageing is not None:
104
104
  args['IFLA_BR_AGEING_TIME'] = ageing
105
+ elif self.TYPE_TUNTAP == dev_type:
106
+ # Set mode (default to 'tap' if not specified)
107
+ # This matches the 'ip tuntap add' command behavior
108
+ tap_mode = mode if mode else 'tap'
109
+ args['mode'] = tap_mode
110
+ # Enable multiqueue if requested
111
+ # This sets the IFF_MULTI_QUEUE flag in IFTUN_IFR
112
+ if multiqueue:
113
+ args['multi_queue'] = True
105
114
  else:
106
115
  raise exception.NetworkInterfaceTypeNotDefined(type=dev_type)
107
116
 
os_vif/objects/vif.py CHANGED
@@ -327,7 +327,8 @@ class VIFPortProfileOpenVSwitch(VIFPortProfileBase):
327
327
  # Version 1.1: Added 'datapath_type'
328
328
  # Version 1.2: VIFPortProfileBase updated to 1.1 from 1.0
329
329
  # Version 1.3: Added 'create_port'
330
- VERSION = '1.3'
330
+ # Version 1.4: Added 'create_tap' and 'multiqueue'
331
+ VERSION = '1.4'
331
332
 
332
333
  fields = {
333
334
  #: A UUID to uniquely identify the interface. If omitted one will be
@@ -342,10 +343,21 @@ class VIFPortProfileOpenVSwitch(VIFPortProfileBase):
342
343
 
343
344
  #: Whether the os-vif plugin should add the port to the bridge.
344
345
  'create_port': fields.BooleanField(default=False),
346
+
347
+ #: Whether the os-vif plugin should create the TAP device.
348
+ 'create_tap': fields.BooleanField(default=False),
349
+
350
+ #: Whether to enable multiqueue on the TAP device.
351
+ 'multiqueue': fields.BooleanField(default=False),
345
352
  }
346
353
 
347
354
  def obj_make_compatible(self, primitive, target_version):
348
355
  target_version = versionutils.convert_version_to_tuple(target_version)
356
+ if target_version < (1, 4):
357
+ if 'create_tap' in primitive:
358
+ del primitive['create_tap']
359
+ if 'multiqueue' in primitive:
360
+ del primitive['multiqueue']
349
361
  if target_version < (1, 3) and 'create_port' in primitive:
350
362
  del primitive['create_port']
351
363
  if target_version < (1, 1) and 'datapath_type' in primitive:
@@ -370,7 +382,8 @@ class VIFPortProfileFPOpenVSwitch(VIFPortProfileOpenVSwitch):
370
382
  # Version 1.1: VIFPortProfileOpenVSwitch updated to 1.1 from 1.0
371
383
  # Version 1.2: VIFPortProfileOpenVSwitch updated to 1.2 from 1.1
372
384
  # Version 1.3: VIFPortProfileOpenVSwitch updated to 1.3 from 1.2
373
- VERSION = '1.3'
385
+ # Version 1.4: VIFPortProfileOpenVSwitch updated to 1.4 from 1.3
386
+ VERSION = '1.4'
374
387
 
375
388
  fields = {
376
389
  #: Name of the bridge (managed by fast path) to connect to.
@@ -391,9 +404,12 @@ class VIFPortProfileFPOpenVSwitch(VIFPortProfileOpenVSwitch):
391
404
  elif target_version < (1, 3):
392
405
  super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible(
393
406
  primitive, '1.2')
394
- else:
407
+ elif target_version < (1, 4):
395
408
  super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible(
396
409
  primitive, '1.3')
410
+ else:
411
+ super(VIFPortProfileFPOpenVSwitch, self).obj_make_compatible(
412
+ primitive, '1.4')
397
413
 
398
414
 
399
415
  @removals.removed_class("VIFPortProfileOVSRepresentor",
@@ -421,7 +437,8 @@ class VIFPortProfileOVSRepresentor(VIFPortProfileOpenVSwitch):
421
437
  # Version 1.1: VIFPortProfileOpenVSwitch updated to 1.1 from 1.0
422
438
  # Version 1.2: VIFPortProfileOpenVSwitch updated to 1.2 from 1.1
423
439
  # Version 1.3: VIFPortProfileOpenVSwitch updated to 1.3 from 1.2
424
- VERSION = '1.3'
440
+ # Version 1.4: VIFPortProfileOpenVSwitch updated to 1.4 from 1.3
441
+ VERSION = '1.4'
425
442
 
426
443
  fields = {
427
444
  #: Name to set on the representor (if set).
@@ -442,9 +459,12 @@ class VIFPortProfileOVSRepresentor(VIFPortProfileOpenVSwitch):
442
459
  elif target_version < (1, 3):
443
460
  super(VIFPortProfileOVSRepresentor, self).obj_make_compatible(
444
461
  primitive, '1.2')
445
- else:
462
+ elif target_version < (1, 4):
446
463
  super(VIFPortProfileOVSRepresentor, self).obj_make_compatible(
447
464
  primitive, '1.3')
465
+ else:
466
+ super(VIFPortProfileOVSRepresentor, self).obj_make_compatible(
467
+ primitive, '1.4')
448
468
 
449
469
 
450
470
  @base.VersionedObjectRegistry.register
@@ -81,6 +81,22 @@ class ShellIpCommands(object):
81
81
  return
82
82
  return match.group('state')
83
83
 
84
+ def is_admin_up(self, device):
85
+ """Check if device is administratively up (UP flag set).
86
+
87
+ This differs from show_state() which returns the operational state.
88
+ TAP devices without a carrier will have state=DOWN but can still
89
+ be administratively UP (UP in flags like <NO-CARRIER,BROADCAST,UP>).
90
+ """
91
+ # Flags are in angle brackets on the first line, e.g. <NO-CARRIER,UP>
92
+ first_line = self.show_device(device)[0]
93
+ regex = re.compile(r".*<(?P<flags>[^>]+)>")
94
+ match = regex.match(first_line)
95
+ if match is None:
96
+ return False
97
+ flags = match.group('flags').split(',')
98
+ return 'UP' in flags
99
+
84
100
  def show_promisc(self, device):
85
101
  regex = re.compile(r".*(PROMISC)")
86
102
  match = regex.match(self.show_device(device)[0])
@@ -263,3 +279,98 @@ class TestIpCommand(ShellIpCommands, base.BaseFunctionalTestCase):
263
279
  _ip_cmd_set(device, master=bridge)
264
280
  path = "/sys/class/net/{}/brif/{}".format(bridge, device)
265
281
  self.assertTrue(os.path.exists(path))
282
+
283
+ def test_add_tap(self):
284
+ """Test creating a tap device."""
285
+ device = "test_tap_1"
286
+ self.addCleanup(self.del_device, device)
287
+ _ip_cmd_add(device, 'tuntap', mode='tap')
288
+ self.assertTrue(self.exist_device(device))
289
+
290
+ # Verify it's a tap device by checking /sys/class/net/<dev>/tun_flags
291
+ tun_flags_path = "/sys/class/net/{}/tun_flags".format(device)
292
+ if os.path.exists(tun_flags_path):
293
+ with open(tun_flags_path, 'r') as f:
294
+ flags_hex = f.read().strip()
295
+ flags = int(flags_hex, 16)
296
+ # IFF_TAP = 0x0002
297
+ IFF_TAP = 0x0002
298
+ self.assertTrue(flags & IFF_TAP,
299
+ "TAP flag not set. Flags: {}".format(flags_hex))
300
+
301
+ def test_add_tap_with_multiqueue(self):
302
+ """Test creating a tap device with multiqueue parameter.
303
+
304
+ Note: The IFF_MULTI_QUEUE flag (0x0100) cannot be set via netlink.
305
+ It is only set when a process opens /dev/net/tun with the
306
+ IFF_MULTI_QUEUE flag in the ioctl. Libvirt sets this when attaching
307
+ to the TAP device. This test verifies that:
308
+ 1. The TAP device can be created with multiqueue=True without error
309
+ 2. The device is properly created as a TAP device
310
+ """
311
+ device = "test_tap_mq"
312
+ self.addCleanup(self.del_device, device)
313
+ _ip_cmd_add(device, 'tuntap', mode='tap', multiqueue=True)
314
+ self.assertTrue(self.exist_device(device))
315
+
316
+ # Verify it's a tap device by checking /sys/class/net/<dev>/tun_flags
317
+ tun_flags_path = "/sys/class/net/{}/tun_flags".format(device)
318
+ if os.path.exists(tun_flags_path):
319
+ with open(tun_flags_path, 'r') as f:
320
+ flags_hex = f.read().strip()
321
+ flags = int(flags_hex, 16)
322
+ # IFF_TAP = 0x0002 - verify it's a TAP device
323
+ IFF_TAP = 0x0002
324
+ self.assertTrue(
325
+ flags & IFF_TAP,
326
+ "TAP flag not set. Flags: %s" % flags_hex)
327
+
328
+ def test_add_tap_idempotent(self):
329
+ """Test tap device creation is idempotent with exit code handling."""
330
+ device = "test_tap_idemp"
331
+ self.addCleanup(self.del_device, device)
332
+
333
+ # Create the device
334
+ _ip_cmd_add(device, 'tuntap', mode='tap')
335
+ self.assertTrue(self.exist_device(device))
336
+
337
+ # Try to create again with EEXIST in check_exit_code (17)
338
+ # This should not raise an exception
339
+ _ip_cmd_add(device, 'tuntap', mode='tap', check_exit_code=[0, 17])
340
+ self.assertTrue(self.exist_device(device))
341
+
342
+ def test_add_tun_device(self):
343
+ """Test creating a tun device (not tap)."""
344
+ device = "test_tun_1"
345
+ self.addCleanup(self.del_device, device)
346
+ _ip_cmd_add(device, 'tuntap', mode='tun')
347
+ self.assertTrue(self.exist_device(device))
348
+
349
+ # Verify it's a tun device
350
+ tun_flags_path = "/sys/class/net/{}/tun_flags".format(device)
351
+ if os.path.exists(tun_flags_path):
352
+ with open(tun_flags_path, 'r') as f:
353
+ flags_hex = f.read().strip()
354
+ flags = int(flags_hex, 16)
355
+ # IFF_TUN = 0x0001
356
+ IFF_TUN = 0x0001
357
+ self.assertTrue(flags & IFF_TUN,
358
+ "TUN flag not set. Flags: {}".format(flags_hex))
359
+
360
+ def test_tap_set_properties(self):
361
+ """Test setting properties on a tap device."""
362
+ device = "test_tap_props"
363
+ mac = "36:a7:e4:f9:01:02"
364
+ self.addCleanup(self.del_device, device)
365
+
366
+ # Create tap device
367
+ _ip_cmd_add(device, 'tuntap', mode='tap')
368
+ self.assertTrue(self.exist_device(device))
369
+
370
+ # Set MAC address and state
371
+ _ip_cmd_set(device, address=mac, state='up')
372
+ # TAP devices without a carrier will show state=DOWN but should be
373
+ # administratively UP (UP flag in interface flags)
374
+ self.assertTrue(self.is_admin_up(device),
375
+ "Device should be administratively UP")
376
+ self.assertEqual(mac, self.show_mac(device))
@@ -175,3 +175,69 @@ class TestIpCommand(base.TestCase):
175
175
 
176
176
  self.assertRaises(ipexc.NetlinkError, self.ip.delete, self.DEVICE,
177
177
  check_exit_code=[self.OTHER_ERROR_CODE])
178
+
179
+ def test_add_tap(self):
180
+ """Test creating a basic tap device."""
181
+ self.ip.add(self.DEVICE, 'tuntap', mode='tap')
182
+ args = {'ifname': self.DEVICE,
183
+ 'kind': 'tuntap',
184
+ 'mode': 'tap'}
185
+ self.ip_link.assert_called_once_with('add', **args)
186
+
187
+ def test_add_tap_default_mode(self):
188
+ """Test creating tap device with default mode (should be 'tap')."""
189
+ self.ip.add(self.DEVICE, 'tuntap')
190
+ args = {'ifname': self.DEVICE,
191
+ 'kind': 'tuntap',
192
+ 'mode': 'tap'}
193
+ self.ip_link.assert_called_once_with('add', **args)
194
+
195
+ def test_add_tap_with_multiqueue(self):
196
+ """Test creating tap device with multiqueue enabled."""
197
+ self.ip.add(self.DEVICE, 'tuntap', mode='tap', multiqueue=True)
198
+
199
+ # Verify the call includes multiqueue flag
200
+ args = {'ifname': self.DEVICE,
201
+ 'kind': 'tuntap',
202
+ 'mode': 'tap',
203
+ 'multi_queue': True}
204
+ self.ip_link.assert_called_once_with('add', **args)
205
+
206
+ def test_add_tun_device(self):
207
+ """Test creating a tun device (not tap)."""
208
+ self.ip.add(self.DEVICE, 'tuntap', mode='tun')
209
+ args = {'ifname': self.DEVICE,
210
+ 'kind': 'tuntap',
211
+ 'mode': 'tun'}
212
+ self.ip_link.assert_called_once_with('add', **args)
213
+
214
+ def test_add_tap_exit_code(self):
215
+ """Test tap device creation with exit code handling."""
216
+ self.ip_link.side_effect = ipexc.NetlinkError(self.ERROR_CODE,
217
+ msg="Error message")
218
+
219
+ # Should not raise when error code matches
220
+ self.ip.add(self.DEVICE, 'tuntap', mode='tap',
221
+ check_exit_code=[self.ERROR_CODE])
222
+ self.ip_link.assert_called_once()
223
+
224
+ # Should raise when exit code doesn't match
225
+ self.assertRaises(
226
+ ipexc.NetlinkError,
227
+ self.ip.add, self.DEVICE, 'tuntap', mode='tap',
228
+ check_exit_code=[self.OTHER_ERROR_CODE])
229
+
230
+ def test_set_no_multiqueue_parameter(self):
231
+ """Verify set() no longer accepts multiqueue parameter."""
232
+ with mock.patch.object(
233
+ iproute.IPRoute, 'link_lookup', return_value=[1],
234
+ create=True):
235
+ self.ip_link.return_value = [{'flags': 0}]
236
+
237
+ # Should work without multiqueue
238
+ self.ip.set(self.DEVICE, state=self.UP, mtu=self.MTU,
239
+ address=self.MAC)
240
+
241
+ # Should raise TypeError if multiqueue is passed
242
+ self.assertRaises(TypeError, self.ip.set, self.DEVICE,
243
+ multiqueue=True)
@@ -40,12 +40,12 @@ object_data = {
40
40
  'VIFPortProfile8021Qbg': '1.1-b3011621809dca9216b50579ce9d6b19',
41
41
  'VIFPortProfile8021Qbh': '1.1-226b61b2e76ba452f7b31530cff80ac9',
42
42
  'VIFPortProfileBase': '1.1-4982d1621df12ebd1f3b07948f3d0e5f',
43
- 'VIFPortProfileOpenVSwitch': '1.3-1ad9a350a9cae19c977d21fcce7c8c7f',
44
- 'VIFPortProfileFPOpenVSwitch': '1.3-06c425743430e7702ef112e09b987346',
43
+ 'VIFPortProfileOpenVSwitch': '1.4-54fb321f2b70c37d392fadc226c889e7',
44
+ 'VIFPortProfileFPOpenVSwitch': '1.4-20904c1dcdac0641cabf7b02525973bd',
45
45
  'VIFPortProfileFPBridge': '1.1-49f1952bf50bab7a95112c908534751f',
46
46
  'VIFPortProfileFPTap': '1.1-fd178229477604dfb65de5ce929488e5',
47
47
  'VIFVHostUser': '1.1-1f95b43be1f884f090ca1f4d79adfd35',
48
- 'VIFPortProfileOVSRepresentor': '1.3-f625e17143473b93d6c7f97ded9f785a',
48
+ 'VIFPortProfileOVSRepresentor': '1.4-82b7292ee4cd6f808b724d2f5648932b',
49
49
  'VIFNestedDPDK': '1.0-fdbaf6b20afd116529929b21aa7158dc',
50
50
  'VIFPortProfileK8sDPDK': '1.1-e2a2abd112b14e0239e76b99d9b252ae',
51
51
  'DatapathOffloadBase': '1.0-77509ea1ea0dd750d5864b9bd87d3f9d',
@@ -78,3 +78,60 @@ class TestObjectVersions(base.TestCase):
78
78
  self.assertIn('vif_name', primitive)
79
79
  vif.obj_make_compatible(primitive, '1.0')
80
80
  self.assertNotIn('vif_name', primitive)
81
+
82
+ def test_vif_port_profile_ovs_obj_make_compatible_1_4(self):
83
+ """Test obj_make_compatible removes create_tap and multiqueue for
84
+ versions < 1.4.
85
+ """
86
+ profile = objects.vif.VIFPortProfileOpenVSwitch(
87
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
88
+ create_tap=True,
89
+ multiqueue=True)
90
+ primitive = profile.obj_to_primitive()['versioned_object.data']
91
+ self.assertIn('create_tap', primitive)
92
+ self.assertIn('multiqueue', primitive)
93
+
94
+ # For version 1.3, both create_tap and multiqueue should be removed
95
+ profile.obj_make_compatible(primitive, '1.3')
96
+ self.assertNotIn('create_tap', primitive)
97
+ self.assertNotIn('multiqueue', primitive)
98
+
99
+ def test_vif_port_profile_ovs_obj_make_compatible_1_3(self):
100
+ """Test obj_make_compatible removes create_port for versions < 1.3."""
101
+ profile = objects.vif.VIFPortProfileOpenVSwitch(
102
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
103
+ create_port=True)
104
+ primitive = profile.obj_to_primitive()['versioned_object.data']
105
+ self.assertIn('create_port', primitive)
106
+
107
+ # For version 1.2, create_port should be removed
108
+ profile.obj_make_compatible(primitive, '1.2')
109
+ self.assertNotIn('create_port', primitive)
110
+
111
+ def test_vif_port_profile_ovs_create_tap_multiqueue_fields(self):
112
+ """Test that create_tap and multiqueue fields can be set."""
113
+ profile = objects.vif.VIFPortProfileOpenVSwitch(
114
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
115
+ create_tap=True,
116
+ multiqueue=True)
117
+ self.assertTrue(profile.create_tap)
118
+ self.assertTrue(profile.multiqueue)
119
+
120
+ # Test explicitly set default values
121
+ profile_default = objects.vif.VIFPortProfileOpenVSwitch(
122
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
123
+ create_tap=False,
124
+ multiqueue=False)
125
+ self.assertFalse(profile_default.create_tap)
126
+ self.assertFalse(profile_default.multiqueue)
127
+
128
+ def test_vif_port_profile_ovs_in_operator(self):
129
+ """Test that 'in' operator works for checking field existence."""
130
+ profile = objects.vif.VIFPortProfileOpenVSwitch(
131
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
132
+ create_tap=True,
133
+ multiqueue=True)
134
+ self.assertIn('create_tap', profile)
135
+ self.assertIn('multiqueue', profile)
136
+ self.assertIn('interface_id', profile)
137
+ self.assertNotIn('nonexistent_field', profile)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: os_vif
3
- Version: 4.2.1
3
+ Version: 4.3.0
4
4
  Summary: A library for plugging and unplugging virtual interfaces in OpenStack.
5
5
  Home-page: https://docs.openstack.org/os-vif/latest/
6
6
  Author: OpenStack
@@ -34,6 +34,16 @@ Requires-Dist: ovsdbapp>=0.12.1
34
34
  Requires-Dist: stevedore>=1.20.0
35
35
  Requires-Dist: debtcollector>=1.19.0
36
36
  Requires-Dist: pyroute2>=0.5.2; sys_platform != "win32"
37
+ Dynamic: author
38
+ Dynamic: author-email
39
+ Dynamic: classifier
40
+ Dynamic: description
41
+ Dynamic: home-page
42
+ Dynamic: license-file
43
+ Dynamic: provides-extra
44
+ Dynamic: requires-dist
45
+ Dynamic: requires-python
46
+ Dynamic: summary
37
47
 
38
48
  ========================
39
49
  Team and repository tags
@@ -8,9 +8,9 @@ os_vif/version.py,sha256=ilQe2xTxPDxlzJYWbq8KdHeashflTfC1OZewgFPKPRI,713
8
8
  os_vif/internal/__init__.py,sha256=VGE1e9o-7RkuTzNhZl4Wivz1tojriLYypVBObhSZfSk,967
9
9
  os_vif/internal/ip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  os_vif/internal/ip/api.py,sha256=BlGJdRe_Yn7P23Dejo7gLK-M6UasY8sKrMCz0FPvx2s,722
11
- os_vif/internal/ip/ip_command.py,sha256=agySNBD4GHp87uo6HbtIg4JwtOMhfuRZN5X7ytPir5k,2787
11
+ os_vif/internal/ip/ip_command.py,sha256=3xOsIOozcZNza78bsAhGHNJTGHiSH9CZSXqwPLeA1oE,3044
12
12
  os_vif/internal/ip/linux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- os_vif/internal/ip/linux/impl_pyroute2.py,sha256=ddQ9d7fzBJEYmE1wJhMfpmHftTBFdJ2C94dCNGRsB0A,5511
13
+ os_vif/internal/ip/linux/impl_pyroute2.py,sha256=ZFJwd5FlHxq_rZmfciRPQSG5KGPZPubPbJU6liTNKGI,6002
14
14
  os_vif/objects/__init__.py,sha256=ePMVrOlb8ti6YmpdZ15pOXI1XLeFjHvhBvw35abZRZE,883
15
15
  os_vif/objects/base.py,sha256=kI0c1gPDN37RhGuOYg18AA7l98uUfpqs14AS9GfE4oE,1139
16
16
  os_vif/objects/fields.py,sha256=i_kVqeCOBfcNKuZiB3gBat-8qJWhLtwEQxsl-k5plgw,1761
@@ -20,7 +20,7 @@ os_vif/objects/instance_info.py,sha256=pdZcq6ZJzT1vFnkeUV_jlCvMNz53QlHRvY-bB1u5n
20
20
  os_vif/objects/network.py,sha256=L6Ngq_u4vbF_xUc9o2aUW2oq0OWALjXFgB1uHsLaZm4,2134
21
21
  os_vif/objects/route.py,sha256=44EbxorPn3QiKusaAoksrYolSR6jCYS7sh5tplHenOE,1352
22
22
  os_vif/objects/subnet.py,sha256=ZSiwVUZGZT_kmYuND4PwpN98nImvpwMc-ne5DO-Lkg0,1458
23
- os_vif/objects/vif.py,sha256=VpVgaXez_SRZvoY0BcoSWQY_nF02tP09o8ZjWKldiLI,21668
23
+ os_vif/objects/vif.py,sha256=DlRuvv0ruI246YtvxZG9oP_1pWC50GhzNszZVysPl20,22606
24
24
  os_vif/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  os_vif/tests/functional/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  os_vif/tests/functional/base.py,sha256=m2uqvH9BkBcCYU7PtlnBYUmwmQLUiuwVS99scJjYQ04,5005
@@ -28,20 +28,22 @@ os_vif/tests/functional/privsep.py,sha256=c7dqBiO4d-hxgCq-DgbP2A7HB6fdhZK4X0ADYU
28
28
  os_vif/tests/functional/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  os_vif/tests/functional/internal/command/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  os_vif/tests/functional/internal/command/ip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py,sha256=J1NsdZAK_OY6dvKlb6HstJAhDvKUOEMzox8sAZGe-mo,9982
31
+ os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py,sha256=hQrZAnV3S6sbp_6QCS4WHmZIZl-udrw0J8c9VCStqZw,14778
32
32
  os_vif/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  os_vif/tests/unit/base.py,sha256=sEjrOSy3yW16WwOorv164ZuUyvKh2TY0gMoVf6lg3kA,795
34
34
  os_vif/tests/unit/test_base.py,sha256=iqgweFRWprY98mkGL3HIbV9D8DuX_tUPH8uRiDflwDc,3137
35
35
  os_vif/tests/unit/test_exception.py,sha256=UDVJSR3CKU3rc5kmN14ilL9Fj0cgnwQbGoP4weo2P4w,1335
36
36
  os_vif/tests/unit/test_host_info.py,sha256=2PYHg4pY-YwUuSg9MyDKJbJhKVGRT6ujJATkzzIPx18,7576
37
- os_vif/tests/unit/test_objects.py,sha256=WflvhMvvCauXexj04tCw47HvsysHX4Q7fsgJKrPNaI8,3709
37
+ os_vif/tests/unit/test_objects.py,sha256=Bpd-edRlIZ7ojvNw4V9SaMQd9ih7lSlrKnHDG8Zb9Ns,6292
38
38
  os_vif/tests/unit/test_os_vif.py,sha256=b0h65Wa9tvxw-e7c707SoJQjfGzEALSFZUPkTtr_GkY,6612
39
39
  os_vif/tests/unit/test_vif.py,sha256=mhg2PZ3iVEPzffO5I0VT1wIUP3c-h7MAqrZgCuZdjxE,18000
40
40
  os_vif/tests/unit/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  os_vif/tests/unit/internal/ip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
42
  os_vif/tests/unit/internal/ip/test_api.py,sha256=LnkAM0M8giNjAw_Q4-oRic3WIW3OWq-SCGlde9SCenY,820
43
43
  os_vif/tests/unit/internal/ip/linux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- os_vif/tests/unit/internal/ip/linux/test_impl_pyroute2.py,sha256=SKLPhV2O9EVXkgE4jVH2pHUt27W7vcNy6iVs2OECR8c,7540
44
+ os_vif/tests/unit/internal/ip/linux/test_impl_pyroute2.py,sha256=9R2YhJlN-DSTYUnR9xLuuN1NlXkwkC1LL0wXnR7b6ck,10207
45
+ os_vif-4.3.0.dist-info/licenses/AUTHORS,sha256=1vx_YLfQyRK6hjRsylsaEcnk3tHr-EeOH2G6htWR224,3434
46
+ os_vif-4.3.0.dist-info/licenses/LICENSE,sha256=XfKg2H1sVi8OoRxoisUlMqoo10TKvHmU_wU39ks7MyA,10143
45
47
  vif_plug_linux_bridge/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
48
  vif_plug_linux_bridge/constants.py,sha256=NC8n4uOMFCYrhq0sM1ZeGBKo-Gs4OMR60cYP47wMRCk,602
47
49
  vif_plug_linux_bridge/iptables.py,sha256=q4LmAMMzg-r9IgDNTu3jCLjNDM8b0JbGo-qmiA2B-JE,20641
@@ -59,9 +61,9 @@ vif_plug_noop/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
59
61
  vif_plug_noop/tests/unit/test_plugin.py,sha256=x8m9zuGtHsPoZRZP6Q6BREAFx0wiEagFtTFlB_i_Bxc,1236
60
62
  vif_plug_ovs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
63
  vif_plug_ovs/constants.py,sha256=vQKnYSeBMjV2RGQPGxk96D8FtgpvJIZEX_yZzUlsGg8,892
62
- vif_plug_ovs/exception.py,sha256=ZiECZVvjCFTVXhuii3uhJn4lZvozKRxziedZB3R9JNo,1328
63
- vif_plug_ovs/linux_net.py,sha256=HvhfSXjeAlU2l9iZNiN8CFr-GL9psS8Hk8_KAaktH6I,13922
64
- vif_plug_ovs/ovs.py,sha256=zvxORVO_bW9_2QgoAYFf7Fdu3VtwcSJz1qJ3aY4TEmg,22920
64
+ vif_plug_ovs/exception.py,sha256=5MQZsoUFZ7kShXQA3Jeax9CcOYpT2I3h-F8ugNMncks,1569
65
+ vif_plug_ovs/linux_net.py,sha256=El3C-9utimXonEE51D5lDthP5gmFkH_XI6jyrcWXOF4,14873
66
+ vif_plug_ovs/ovs.py,sha256=_j3iCYnvwNemyR_vfAH3xMkWCCvbEWesRA6vrvi-uRE,25386
65
67
  vif_plug_ovs/privsep.py,sha256=nPQUkYgjbSjaHMuu40fZArRnf8RBiR73l4YpDF1K1yQ,1100
66
68
  vif_plug_ovs/ovsdb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
69
  vif_plug_ovs/ovsdb/api.py,sha256=_jHR8xokDAPI8sw3IEWuT3hObJuskqVzeVMDaWRbIq0,1293
@@ -71,19 +73,17 @@ vif_plug_ovs/ovsdb/ovsdb_lib.py,sha256=U7mZk_1q5tnHgWsgBotIqt_-fu7I3pfBGapchmlLc
71
73
  vif_plug_ovs/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
74
  vif_plug_ovs/tests/functional/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
75
  vif_plug_ovs/tests/functional/base.py,sha256=GZKyA9UR9BaEgZ0tv6xyMTQnz9FpKedV2do1ePMB-rw,2059
74
- vif_plug_ovs/tests/functional/test_plugin.py,sha256=1zVCielOSCKjn6NMMYfxEp1t-nmTUHGciPZeJghZd8c,10447
76
+ vif_plug_ovs/tests/functional/test_plugin.py,sha256=f2XdYkZPiOL5hzzXCM3bMwGw8CMsbjWkA_HvcIFyZZk,13018
75
77
  vif_plug_ovs/tests/functional/ovsdb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
- vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py,sha256=tXSTmjYWOmqLXYQ42_3Sv8XNNktlDxPDalJRr8KRwCU,13775
78
+ vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py,sha256=dSmQyqzFn9VSgxEeHr5EHePnhfx_SvlA5IxX1gGGAes,13517
77
79
  vif_plug_ovs/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
- vif_plug_ovs/tests/unit/test_linux_net.py,sha256=B3buNpn9EKZQPio5rNrIma_E5MUL9vBspdg5EqZ3RHk,17532
79
- vif_plug_ovs/tests/unit/test_plugin.py,sha256=YfJXJirOWbZB2l2VHTiJPCMZlgBCyRXDAFgzEj6pQeY,30827
80
+ vif_plug_ovs/tests/unit/test_linux_net.py,sha256=TQ4If-mNl2Q8-JoIrNH8QRPO2jQDTkKlSvwKawsgsSs,19703
81
+ vif_plug_ovs/tests/unit/test_plugin.py,sha256=3tH3jpazI83m9TQaScQ-o9NHSO1_2Fk29LHV9Q_kT4M,43506
80
82
  vif_plug_ovs/tests/unit/ovsdb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
83
  vif_plug_ovs/tests/unit/ovsdb/test_ovsdb_lib.py,sha256=mec9WESL92MQZonDVyBrsJJveMcLyvHKPj1GU8yYGjo,10687
82
- os_vif-4.2.1.dist-info/AUTHORS,sha256=nljv1JcwR_ww3Wbk3qLgNHa-b2DwpSggdnTI0REYvyo,3356
83
- os_vif-4.2.1.dist-info/LICENSE,sha256=XfKg2H1sVi8OoRxoisUlMqoo10TKvHmU_wU39ks7MyA,10143
84
- os_vif-4.2.1.dist-info/METADATA,sha256=py0CxkiQZ-llyYDhoMOak5u6Kd6NrZ0DecEn87qQbPw,2324
85
- os_vif-4.2.1.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
86
- os_vif-4.2.1.dist-info/entry_points.txt,sha256=BU9WplZnF_bjwT4SQP5n1mKu2f0R5xXBOznXP1GygHM,206
87
- os_vif-4.2.1.dist-info/pbr.json,sha256=9Bn0S-V8G7QyTsM_UW7im9HossXmtpKDvFHkOwabN50,46
88
- os_vif-4.2.1.dist-info/top_level.txt,sha256=ULBxtkTk3bkfzCSYJjifWehfjJdMODVzC6SX5l_CNKo,56
89
- os_vif-4.2.1.dist-info/RECORD,,
84
+ os_vif-4.3.0.dist-info/METADATA,sha256=9GHnBKuER01IWkhNAktrSm96MNkjK6-K8RxXoFMLw9g,2533
85
+ os_vif-4.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
86
+ os_vif-4.3.0.dist-info/entry_points.txt,sha256=BU9WplZnF_bjwT4SQP5n1mKu2f0R5xXBOznXP1GygHM,206
87
+ os_vif-4.3.0.dist-info/pbr.json,sha256=5Ah5X1ihi7jQlwKHCYVuLIcFpYUEB-NSbfUFgmYOH2o,46
88
+ os_vif-4.3.0.dist-info/top_level.txt,sha256=ULBxtkTk3bkfzCSYJjifWehfjJdMODVzC6SX5l_CNKo,56
89
+ os_vif-4.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.2)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,8 +1,10 @@
1
1
  Adrian Chiris <adrianc@mellanox.com>
2
+ Adrien Cunin <a.cunin@syseleven.de>
2
3
  Alin Balutoiu <abalutoiu@cloudbasesolutions.com>
3
4
  Andreas Jaeger <aj@suse.com>
4
5
  Balazs Gibizer <gibi@redhat.com>
5
6
  Bence Romsics <bence.romsics@gmail.com>
7
+ Bodo Petermann <b.petermann@syseleven.de>
6
8
  Brian Haley <brian.haley@hpe.com>
7
9
  Cao Xuan Hoang <hoangcx@vn.fujitsu.com>
8
10
  Carlos Goncalves <cgoncalves@redhat.com>
@@ -0,0 +1 @@
1
+ {"git_version": "8cbe6ee", "is_release": true}
vif_plug_ovs/exception.py CHANGED
@@ -35,3 +35,9 @@ class RepresentorNotFound(osv_exception.ExceptionBase):
35
35
 
36
36
  class PciDeviceNotFoundById(osv_exception.ExceptionBase):
37
37
  msg_fmt = _("PCI device %(id)s not found")
38
+
39
+
40
+ class TapCreationNotSupported(osv_exception.ExceptionBase):
41
+ msg_fmt = _('Tap device creation requested for unsupported VIF type. '
42
+ 'create_tap is only supported for VIFOpenVSwitch, got '
43
+ '%(vif_type)s')
vif_plug_ovs/linux_net.py CHANGED
@@ -90,6 +90,29 @@ def update_veth_pair(dev1_name, dev2_name, mtu):
90
90
  _update_device_mtu(dev, mtu)
91
91
 
92
92
 
93
+ @privsep.vif_plug.entrypoint
94
+ def create_tap(dev, mtu, mac, multiqueue=False):
95
+ """Create a tap device with the specified configuration.
96
+
97
+ Creates a tap device using pyroute2's netlink interface, with optional
98
+ multiqueue support for better performance with multiple VCPUs.
99
+
100
+ :param dev: Device name (string)
101
+ :param mtu: MTU value (integer)
102
+ :param mac: MAC address (string)
103
+ :param multiqueue: Enable multiqueue support (boolean, default False)
104
+ Requires Linux kernel 3.8+
105
+ """
106
+ # Create the tap device with optional multiqueue support
107
+ # Use check_exit_code=[0, 17] to handle EEXIST (device already exists)
108
+ ip_lib.add(dev, 'tuntap', mode='tap', multiqueue=multiqueue,
109
+ check_exit_code=[0, 17])
110
+ # Configure the device state and MAC address
111
+ ip_lib.set(dev, state='up', address=mac, check_exit_code=[0, 2, 254])
112
+ # Set MTU if specified
113
+ _update_device_mtu(dev, mtu)
114
+
115
+
93
116
  def _disable_ipv6(bridge):
94
117
  """Disable ipv6 if available for bridge. Must be called from
95
118
  privsep context.
vif_plug_ovs/ovs.py CHANGED
@@ -223,7 +223,7 @@ class OvsPlugin(plugin.PluginBase):
223
223
  # This is a mitigation for the performance regression
224
224
  # introduced by the fix for bug #1734320. See bug #2017868
225
225
  # for more details.
226
- if not self.ovsdb.port_exists(vif_name, vif.network.bridge):
226
+ if not self.ovsdb.port_exists(vif_name, bridge):
227
227
  kwargs['qos_type'] = qos_type
228
228
  self.ovsdb.create_ovs_vif_port(
229
229
  bridge,
@@ -232,11 +232,62 @@ class OvsPlugin(plugin.PluginBase):
232
232
  vif.address, instance_info.uuid,
233
233
  mtu=mtu,
234
234
  **kwargs)
235
+ # Check if tap creation is requested:
236
+ # - 'field in profile.fields' checks if field exists in schema
237
+ # - 'field in profile' checks if the attribute is set on instance
238
+ profile = vif.port_profile
239
+ create_tap = (
240
+ isinstance(profile, objects.vif.VIFPortProfileOpenVSwitch) and
241
+ 'create_tap' in profile.fields and
242
+ 'create_tap' in profile and
243
+ profile.create_tap
244
+ )
245
+
246
+ if create_tap:
247
+ # Validate VIF type - only VIFOpenVSwitch supports tap creation
248
+ if not isinstance(vif, objects.vif.VIFOpenVSwitch):
249
+ raise exception.TapCreationNotSupported(
250
+ vif_type=vif.__class__.__name__)
251
+
252
+ # Get multiqueue setting from port profile if available
253
+ # 'field in profile.fields' checks schema, 'field in profile'
254
+ # checks if the attribute is set
255
+ multiqueue = ('multiqueue' in profile.fields and
256
+ 'multiqueue' in profile and
257
+ profile.multiqueue)
258
+
259
+ # Create the tap device with proper MAC and MTU if it doesn't
260
+ # already exist (e.g., from a previous plug during init_host)
261
+ if not ip_lib.exists(vif_name):
262
+ linux_net.create_tap(
263
+ vif_name, mtu, vif.address, multiqueue=multiqueue)
235
264
 
236
265
  def _update_vif_port(self, vif, vif_name):
237
266
  mtu = self._get_mtu(vif)
238
267
  self.ovsdb.update_ovs_vif_port(vif_name, mtu)
239
268
 
269
+ def _delete_tap_if_required(self, vif, vif_name):
270
+ """Delete tap device if it was created via create_tap flag.
271
+
272
+ :param vif: VIF object
273
+ :param vif_name: Name of the tap device
274
+ """
275
+ # Check if this VIF had a tap device created for it:
276
+ # - 'field in profile.fields' checks if field exists in schema
277
+ # - 'field in profile' checks if the attribute is set on instance
278
+ profile = getattr(vif, 'port_profile', None)
279
+ create_tap = (
280
+ profile is not None and
281
+ isinstance(profile, objects.vif.VIFPortProfileOpenVSwitch) and
282
+ isinstance(vif, objects.vif.VIFOpenVSwitch) and
283
+ 'create_tap' in profile.fields and
284
+ 'create_tap' in profile and
285
+ profile.create_tap
286
+ )
287
+
288
+ if create_tap and ip_lib.exists(vif_name):
289
+ linux_net.delete_net_dev(vif_name)
290
+
240
291
  @staticmethod
241
292
  def _get_vif_datapath_type(vif, datapath=constants.OVS_DATAPATH_SYSTEM):
242
293
  profile = vif.port_profile
@@ -428,6 +479,9 @@ class OvsPlugin(plugin.PluginBase):
428
479
 
429
480
  def _unplug_port_bridge(self, vif, instance_info):
430
481
  """Create a per-VIF OVS bridge and patch pair."""
482
+ # Delete tap device if it was created
483
+ self._delete_tap_if_required(vif, vif.vif_name)
484
+
431
485
  # NOTE(sean-k-mooney): the port name prefix should not be
432
486
  # changed to avoid loosing ports on upgrade.
433
487
  port_bridge_name = self.gen_port_name('pb', vif.id)
@@ -444,6 +498,9 @@ class OvsPlugin(plugin.PluginBase):
444
498
 
445
499
  def _unplug_vif_generic(self, vif, instance_info):
446
500
  """Remove port from OVS."""
501
+ # Delete tap device if it was created
502
+ self._delete_tap_if_required(vif, vif.vif_name)
503
+
447
504
  # NOTE(sean-k-mooney): even with the partial revert of change
448
505
  # Iaf15fa7a678ec2624f7c12f634269c465fbad930 this should be correct
449
506
  # so this is not removed.
@@ -183,12 +183,7 @@ class TestOVSDBLib(testscenarios.WithScenarios,
183
183
  'Interface', port_bridge_port, 'options', port_opts)
184
184
 
185
185
  def test_create_ovs_vif_port_with_default_qos(self):
186
- if self.interface == 'native':
187
- self.skipTest(
188
- 'test_create_ovs_vif_port_with_default_qos is unstable '
189
- 'when run with the native driver, see: '
190
- 'https://bugs.launchpad.net/os-vif/+bug/2087982')
191
- port_name = 'qos-port-' + self.interface
186
+ port_name = 'def-qos-port-' + self.interface
192
187
  iface_id = 'iface_id'
193
188
  mac = 'ca:fe:ca:fe:ca:fe'
194
189
  instance_id = uuidutils.generate_uuid()
@@ -186,6 +186,63 @@ class TestOVSPlugin(testscenarios.WithScenarios,
186
186
  'QoS', str(qos_uuid), 'type', None
187
187
  )
188
188
 
189
+ def test_plug_unplug_ovs_port_with_qos_per_port_bridge(self):
190
+ with mock.patch.object(self.plugin.config, 'per_port_bridge', True):
191
+ bridge = 'br-ppb-' + self.interface
192
+ vif_name = 'port-ppb-' + self.interface
193
+ qos_type = CONF.os_vif_ovs.default_qos_type
194
+
195
+ network = objects.network.Network(
196
+ id='6977aa43-b7c3-484a-8bcb-09d77374981b',
197
+ bridge=bridge,
198
+ subnets=self.subnets,
199
+ vlan=99)
200
+ vif = objects.vif.VIFOpenVSwitch(
201
+ id='e5cf7112-a72f-43a4-aaa3-48a5cfbdeaca',
202
+ address='ca:fe:de:ad:be:ef',
203
+ network=network,
204
+ port_profile=self.profile_ovs_system,
205
+ vif_name=vif_name)
206
+ port_bridge_name = self.plugin.gen_port_name('pb', vif.id)
207
+
208
+ self.addCleanup(self._del_bridge, bridge)
209
+ self.addCleanup(self._del_bridge, port_bridge_name)
210
+ self.addCleanup(
211
+ self.ovs.delete_ovs_vif_port, port_bridge_name, vif_name,
212
+ delete_netdev=False, qos_type=qos_type
213
+ )
214
+ self.addCleanup(del_device, vif_name)
215
+ add_device(vif_name, 'dummy')
216
+ # plugging a vif will create the port and bridges
217
+ # if they don't exist
218
+ self.plugin.plug(vif, self.instance)
219
+ self.assertTrue(self._check_bridge(bridge))
220
+ self.assertTrue(self._check_bridge(port_bridge_name))
221
+ self.assertTrue(self._check_port(vif_name, port_bridge_name))
222
+
223
+ # Plugging a second time should succeed
224
+ self.plugin.plug(vif, self.instance)
225
+
226
+ # Check that the 2nd plug did not create a 2nd qos row,
227
+ # which happened in https://bugs.launchpad.net/os-vif/+bug/2133225
228
+ qos = self.ovs.get_qos(vif_name, qos_type)
229
+ self.assertEqual(1, len(qos))
230
+
231
+ qos_uuid = qos[0]['_uuid']
232
+ self._check_parameter('Port', vif_name, 'qos', qos_uuid)
233
+ self._check_parameter(
234
+ 'QoS', str(qos_uuid), 'type', qos_type
235
+ )
236
+ # unplugging a port will not delete the int bridge,
237
+ # only the per-port bridge.
238
+ self.plugin.unplug(vif, self.instance)
239
+ self.assertTrue(self._check_bridge(bridge))
240
+ self.assertFalse(self._check_bridge(port_bridge_name))
241
+ self.assertFalse(self._check_port(vif_name, bridge))
242
+ self._check_parameter(
243
+ 'QoS', str(qos_uuid), 'type', None
244
+ )
245
+
189
246
  def test_plug_br_int_isolate_vif_dead_vlan(self):
190
247
  with mock.patch.object(self.plugin.config, 'isolate_vif', True):
191
248
  network = objects.network.Network(
@@ -396,3 +396,47 @@ class LinuxNetTest(testtools.TestCase):
396
396
  mock_isfile.return_value = False
397
397
  phys_port_name = linux_net._get_phys_switch_id("ifname")
398
398
  self.assertIsNone(phys_port_name)
399
+
400
+ @mock.patch.object(linux_net, "_update_device_mtu")
401
+ @mock.patch.object(ip_lib, "set")
402
+ @mock.patch.object(ip_lib, "add")
403
+ def test_create_tap(self, mock_add, mock_set, mock_update_mtu):
404
+ """Test basic tap device creation."""
405
+ linux_net.create_tap("tap0", 1500, "aa:bb:cc:dd:ee:ff",
406
+ multiqueue=False)
407
+
408
+ mock_add.assert_called_once_with("tap0", "tuntap", mode="tap",
409
+ multiqueue=False,
410
+ check_exit_code=[0, 17])
411
+ mock_set.assert_called_once_with("tap0", state="up",
412
+ address="aa:bb:cc:dd:ee:ff",
413
+ check_exit_code=[0, 2, 254])
414
+ mock_update_mtu.assert_called_once_with("tap0", 1500)
415
+
416
+ @mock.patch.object(linux_net, "_update_device_mtu")
417
+ @mock.patch.object(ip_lib, "set")
418
+ @mock.patch.object(ip_lib, "add")
419
+ def test_create_tap_with_multiqueue(self, mock_add, mock_set,
420
+ mock_update_mtu):
421
+ """Test tap device creation with multiqueue enabled."""
422
+ linux_net.create_tap("tap0", 1500, "aa:bb:cc:dd:ee:ff",
423
+ multiqueue=True)
424
+
425
+ mock_add.assert_called_once_with("tap0", "tuntap", mode="tap",
426
+ multiqueue=True,
427
+ check_exit_code=[0, 17])
428
+ mock_set.assert_called_once_with("tap0", state="up",
429
+ address="aa:bb:cc:dd:ee:ff",
430
+ check_exit_code=[0, 2, 254])
431
+ mock_update_mtu.assert_called_once_with("tap0", 1500)
432
+
433
+ @mock.patch.object(linux_net, "_update_device_mtu")
434
+ @mock.patch.object(ip_lib, "set")
435
+ @mock.patch.object(ip_lib, "add")
436
+ def test_create_tap_no_mtu(self, mock_add, mock_set, mock_update_mtu):
437
+ """Test tap device creation without MTU."""
438
+ linux_net.create_tap("tap0", None, "aa:bb:cc:dd:ee:ff")
439
+
440
+ mock_add.assert_called_once()
441
+ mock_set.assert_called_once()
442
+ mock_update_mtu.assert_called_once_with("tap0", None)
@@ -19,6 +19,7 @@ from os_vif import objects
19
19
  from os_vif.objects import fields
20
20
 
21
21
  from vif_plug_ovs import constants
22
+ from vif_plug_ovs import exception
22
23
  from vif_plug_ovs import linux_net
23
24
  from vif_plug_ovs import ovs
24
25
  from vif_plug_ovs.ovsdb import ovsdb_lib
@@ -96,6 +97,13 @@ class PluginTest(testtools.TestCase):
96
97
  vif_name='tap-xxx-yyy-zzz',
97
98
  port_profile=self.profile_ovs)
98
99
 
100
+ self.vif_ovs_system = objects.vif.VIFOpenVSwitch(
101
+ id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
102
+ address='ca:fe:de:ad:be:ef',
103
+ network=self.network_ovs,
104
+ vif_name='tap-xxx-yyy-zzz',
105
+ port_profile=self.profile_ovs_system)
106
+
99
107
  # This is used for ironic with vif_type=smartnic
100
108
  self.vif_ovs_smart_nic = objects.vif.VIFOpenVSwitch(
101
109
  id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
@@ -241,6 +249,88 @@ class PluginTest(testtools.TestCase):
241
249
  mtu=plugin.config.network_device_mtu,
242
250
  interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE)
243
251
 
252
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port')
253
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists')
254
+ def test_create_vif_port_qos_port_bridge_true_port_new(
255
+ self, mock_port_exists, mock_create_ovs_vif_port):
256
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
257
+ mock_port_exists.return_value = False
258
+ port_bridge_name = "port-bridge-xxx"
259
+ # _create_vif_port as from _plug_port_bridge
260
+ plugin._create_vif_port(
261
+ self.vif_ovs_system, mock.sentinel.vif_name, self.instance,
262
+ bridge=port_bridge_name, set_ids=False)
263
+ # port existence should be checked on the per-port bridge
264
+ mock_port_exists.assert_called_once_with(
265
+ mock.sentinel.vif_name, port_bridge_name)
266
+ # qos_type should be set for the new port
267
+ mock_create_ovs_vif_port.assert_called_once_with(
268
+ port_bridge_name, mock.sentinel.vif_name,
269
+ self.vif_ovs_system.port_profile.interface_id,
270
+ self.vif_ovs_system.address, self.instance.uuid,
271
+ mtu=plugin.config.network_device_mtu,
272
+ set_ids=False,
273
+ qos_type="linux-noop")
274
+
275
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port')
276
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists')
277
+ def test_create_vif_port_qos_port_bridge_true_port_exists(
278
+ self, mock_port_exists, mock_create_ovs_vif_port):
279
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
280
+ mock_port_exists.return_value = True
281
+ port_bridge_name = "port-bridge-xxx"
282
+ # _create_vif_port as from _plug_port_bridge
283
+ plugin._create_vif_port(
284
+ self.vif_ovs_system, mock.sentinel.vif_name, self.instance,
285
+ bridge=port_bridge_name, set_ids=False)
286
+ # port existence should be checked on the per-port bridge
287
+ mock_port_exists.assert_called_once_with(
288
+ mock.sentinel.vif_name, port_bridge_name)
289
+ # qos_type should not be set for the existing port
290
+ mock_create_ovs_vif_port.assert_called_once_with(
291
+ port_bridge_name, mock.sentinel.vif_name,
292
+ self.vif_ovs_system.port_profile.interface_id,
293
+ self.vif_ovs_system.address, self.instance.uuid,
294
+ mtu=plugin.config.network_device_mtu,
295
+ set_ids=False)
296
+
297
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port')
298
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists')
299
+ def test_create_vif_port_qos_port_bridge_false_port_new(
300
+ self, mock_port_exists, mock_create_ovs_vif_port):
301
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
302
+ mock_port_exists.return_value = False
303
+ # _create_vif_port as from _plug_vif_generic
304
+ plugin._create_vif_port(
305
+ self.vif_ovs_system, mock.sentinel.vif_name, self.instance)
306
+ mock_port_exists.assert_called_once_with(
307
+ mock.sentinel.vif_name, self.vif_ovs_system.network.bridge)
308
+ # qos_type should be set for the new port
309
+ mock_create_ovs_vif_port.assert_called_once_with(
310
+ self.vif_ovs_system.network.bridge, mock.sentinel.vif_name,
311
+ self.vif_ovs_system.port_profile.interface_id,
312
+ self.vif_ovs_system.address, self.instance.uuid,
313
+ mtu=plugin.config.network_device_mtu,
314
+ qos_type="linux-noop")
315
+
316
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port')
317
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists')
318
+ def test_create_vif_port_qos_port_bridge_false_port_exists(
319
+ self, mock_port_exists, mock_create_ovs_vif_port):
320
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
321
+ mock_port_exists.return_value = True
322
+ # _create_vif_port as from _plug_vif_generic
323
+ plugin._create_vif_port(
324
+ self.vif_ovs_system, mock.sentinel.vif_name, self.instance)
325
+ mock_port_exists.assert_called_once_with(
326
+ mock.sentinel.vif_name, self.vif_ovs_system.network.bridge)
327
+ # qos_type should not be set for the existing port
328
+ mock_create_ovs_vif_port.assert_called_once_with(
329
+ self.vif_ovs_system.network.bridge, mock.sentinel.vif_name,
330
+ self.vif_ovs_system.port_profile.interface_id,
331
+ self.vif_ovs_system.address, self.instance.uuid,
332
+ mtu=plugin.config.network_device_mtu)
333
+
244
334
  @mock.patch.object(ovs.OvsPlugin, '_plug_vif_generic')
245
335
  def test_plug_ovs_port_bridge_false(self, plug_vif_generic):
246
336
  plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
@@ -648,3 +738,179 @@ class PluginTest(testtools.TestCase):
648
738
  plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
649
739
  plugin.unplug(self.vif_ovs, self.instance)
650
740
  m_unplug_generic.assert_called_once()
741
+
742
+ @mock.patch.object(linux_net, 'create_tap')
743
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port')
744
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists')
745
+ def test_create_vif_port_with_tap_creation(
746
+ self, mock_port_exists, mock_create_ovs_vif_port, mock_create_tap):
747
+ """Test that create_tap is called when create_tap flag is set."""
748
+ # Create a profile with create_tap=True
749
+ profile_with_tap = objects.vif.VIFPortProfileOpenVSwitch(
750
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
751
+ create_tap=True)
752
+ vif_with_tap = objects.vif.VIFOpenVSwitch(
753
+ id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
754
+ address='ca:fe:de:ad:be:ef',
755
+ network=self.network_ovs,
756
+ vif_name='tap-xxx-yyy-zzz',
757
+ port_profile=profile_with_tap)
758
+
759
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
760
+ mock_port_exists.return_value = False
761
+ plugin._create_vif_port(
762
+ vif_with_tap, 'tap-xxx-yyy-zzz', self.instance)
763
+
764
+ # Verify create_tap was called with correct parameters
765
+ mock_create_tap.assert_called_once_with(
766
+ 'tap-xxx-yyy-zzz',
767
+ plugin.config.network_device_mtu,
768
+ 'ca:fe:de:ad:be:ef',
769
+ multiqueue=False)
770
+
771
+ @mock.patch.object(linux_net, 'create_tap')
772
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port')
773
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists')
774
+ def test_create_vif_port_with_tap_and_multiqueue(
775
+ self, mock_port_exists, mock_create_ovs_vif_port, mock_create_tap):
776
+ """Test that create_tap is called with multiqueue when both are set."""
777
+ # Create a profile with create_tap=True and multiqueue=True
778
+ profile_with_tap_mq = objects.vif.VIFPortProfileOpenVSwitch(
779
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
780
+ create_tap=True,
781
+ multiqueue=True)
782
+ vif_with_tap_mq = objects.vif.VIFOpenVSwitch(
783
+ id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
784
+ address='ca:fe:de:ad:be:ef',
785
+ network=self.network_ovs,
786
+ vif_name='tap-xxx-yyy-zzz',
787
+ port_profile=profile_with_tap_mq)
788
+
789
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
790
+ mock_port_exists.return_value = False
791
+ plugin._create_vif_port(
792
+ vif_with_tap_mq, 'tap-xxx-yyy-zzz', self.instance)
793
+
794
+ # Verify create_tap was called with multiqueue=True
795
+ mock_create_tap.assert_called_once_with(
796
+ 'tap-xxx-yyy-zzz',
797
+ plugin.config.network_device_mtu,
798
+ 'ca:fe:de:ad:be:ef',
799
+ multiqueue=True)
800
+
801
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port')
802
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists')
803
+ def test_create_vif_port_tap_not_supported_vhostuser(
804
+ self, mock_port_exists, mock_create_ovs_vif_port):
805
+ """Test that TapCreationNotSupported is raised for VIFVHostUser."""
806
+ # Create a VIFVHostUser with create_tap=True
807
+ profile_with_tap = objects.vif.VIFPortProfileOpenVSwitch(
808
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
809
+ create_tap=True)
810
+ vif_vhostuser_with_tap = objects.vif.VIFVHostUser(
811
+ id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
812
+ address='ca:fe:de:ad:be:ef',
813
+ network=self.network_ovs,
814
+ path='/var/run/openvswitch/vhub679325f-ca',
815
+ mode='client',
816
+ port_profile=profile_with_tap)
817
+
818
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
819
+ mock_port_exists.return_value = False
820
+
821
+ # Verify exception is raised
822
+ self.assertRaises(
823
+ exception.TapCreationNotSupported,
824
+ plugin._create_vif_port,
825
+ vif_vhostuser_with_tap, 'vhub679325f-ca', self.instance)
826
+
827
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port')
828
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'port_exists')
829
+ def test_create_vif_port_tap_not_supported_hostdevice(
830
+ self, mock_port_exists, mock_create_ovs_vif_port):
831
+ """Test that TapCreationNotSupported is raised for VIFHostDevice."""
832
+ # Create a VIFHostDevice with create_tap=True
833
+ profile_with_tap = objects.vif.VIFPortProfileOpenVSwitch(
834
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
835
+ create_tap=True)
836
+ vif_hostdevice_with_tap = objects.vif.VIFHostDevice(
837
+ id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
838
+ address='ca:fe:de:ad:be:ef',
839
+ network=self.network_ovs,
840
+ dev_type=fields.VIFHostDeviceDevType.ETHERNET,
841
+ dev_address='0002:24:12.3',
842
+ port_profile=profile_with_tap)
843
+
844
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
845
+ mock_port_exists.return_value = False
846
+
847
+ # Verify exception is raised
848
+ self.assertRaises(
849
+ exception.TapCreationNotSupported,
850
+ plugin._create_vif_port,
851
+ vif_hostdevice_with_tap, 'tap-xxx-yyy-zzz', self.instance)
852
+
853
+ @mock.patch.object(ip_lib, 'exists', return_value=True)
854
+ @mock.patch.object(linux_net, 'delete_net_dev')
855
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port')
856
+ def test_unplug_vif_generic_deletes_tap(
857
+ self, mock_delete_ovs_vif_port, mock_delete_net_dev,
858
+ mock_exists):
859
+ """Test that tap device is deleted when unplugging with
860
+ create_tap=True.
861
+ """
862
+ # Create a VIF with create_tap=True
863
+ profile_with_tap = objects.vif.VIFPortProfileOpenVSwitch(
864
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
865
+ create_tap=True)
866
+ vif_with_tap = objects.vif.VIFOpenVSwitch(
867
+ id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
868
+ address='ca:fe:de:ad:be:ef',
869
+ network=self.network_ovs,
870
+ vif_name='tap-xxx-yyy-zzz',
871
+ port_profile=profile_with_tap)
872
+
873
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
874
+ plugin._unplug_vif_generic(vif_with_tap, self.instance)
875
+
876
+ # Verify delete_net_dev was called with vif_name
877
+ mock_delete_net_dev.assert_called_once_with('tap-xxx-yyy-zzz')
878
+
879
+ @mock.patch.object(linux_net, 'delete_net_dev')
880
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port')
881
+ def test_unplug_vif_generic_no_tap_deletion_when_not_created(
882
+ self, mock_delete_ovs_vif_port, mock_delete_net_dev):
883
+ """Test that tap device is not deleted when create_tap=False."""
884
+ # Use default vif_ovs which has create_tap=False (or unset)
885
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
886
+ plugin._unplug_vif_generic(self.vif_ovs, self.instance)
887
+
888
+ # Verify delete_net_dev was not called
889
+ mock_delete_net_dev.assert_not_called()
890
+
891
+ @mock.patch.object(ip_lib, 'exists', return_value=True)
892
+ @mock.patch.object(linux_net, 'delete_net_dev')
893
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port')
894
+ @mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge')
895
+ def test_unplug_port_bridge_deletes_tap(
896
+ self, mock_delete_ovs_bridge, mock_delete_ovs_vif_port,
897
+ mock_delete_net_dev, mock_exists):
898
+ """Test that tap device is deleted when unplugging port bridge with
899
+ create_tap=True.
900
+ """
901
+ # Create a VIF with create_tap=True
902
+ profile_with_tap = objects.vif.VIFPortProfileOpenVSwitch(
903
+ interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
904
+ create_tap=True)
905
+ vif_with_tap = objects.vif.VIFOpenVSwitch(
906
+ id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
907
+ address='ca:fe:de:ad:be:ef',
908
+ network=self.network_ovs,
909
+ vif_name='tap-xxx-yyy-zzz',
910
+ port_profile=profile_with_tap)
911
+
912
+ plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
913
+ plugin._unplug_port_bridge(vif_with_tap, self.instance)
914
+
915
+ # Verify delete_net_dev was called with vif_name
916
+ mock_delete_net_dev.assert_called_once_with('tap-xxx-yyy-zzz')
@@ -1 +0,0 @@
1
- {"git_version": "b33415b", "is_release": true}