Skip to content

Commit a7fae37

Browse files
Allow per-port modification of vnic_type and profile
Today neutronv2's bind_ports_to_host() method is being used by the conductor during live-migration to bind neutron ports to the destination host when neutron port binding API extention is supported[1]. Until now, modifying the vnic_type and profile was not required as the only vnic_type which was officially supported for live-migration is the 'normal' vnic_type. Port profile updates were not required. Support for live-migration with SR-IOV ports requires updates to the port's profile with host specific information e.g the claimed PCI device address on the destination node. - This change modifies bind_ports_to_host to accept a per port dictionary for both vnic_type and port profile. Allowing the user to override each attribute on a per-port basis. - This change updates bind_ports_to_host to generate a separate payload per vif instead of assuming all vifs attached to an interface share the same vnic_type and profile data. - The change aims to extend the existing logic of bind_ports_to_host to allow the flexibility for the user to augment port attributes on a per-port basis. The change does not protect the user from wrongfully calling this method. [1] https://blueprints.launchpad.net/nova/+spec/neutron-new-port-binding-api Change-Id: I958685ee20676d45e5fbdf020b82d5844dcc85fe Partial-Implements: blueprint libvirt-neutron-sriov-livemigration Co-Authored-By: Adrian Chiris <[email protected]>
1 parent 2a3179a commit a7fae37

File tree

3 files changed

+84
-28
lines changed

3 files changed

+84
-28
lines changed

nova/network/base_api.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ def supports_port_binding_extension(self, context):
399399
return False
400400

401401
def bind_ports_to_host(self, context, instance, host,
402-
vnic_type=None, profile=None):
402+
vnic_types=None, port_profiles=None):
403403
"""Attempts to bind the ports from the instance on the given host
404404
405405
If the ports are already actively bound to another host, like the
@@ -420,17 +420,17 @@ def bind_ports_to_host(self, context, instance, host,
420420
:param host: the host on which to bind the ports which
421421
are attached to the instance
422422
:type host: str
423-
:param vnic_type: optional vnic type string for the host
424-
port binding
425-
:type vnic_type: str
426-
:param profile: optional vif profile dict for the host port
427-
binding; note that the port binding profile is mutable
423+
:param vnic_types: optional dict for the host port binding
424+
:type vnic_types: dict of <port_id> : <vnic_type>
425+
:param port_profiles: optional dict per port ID for the host port
426+
binding profile.
427+
note that the port binding profile is mutable
428428
via the networking "Port Binding" API so callers that
429429
pass in a profile should ensure they have the latest
430430
version from neutron with their changes merged,
431431
which can be determined using the "revision_number"
432432
attribute of the port.
433-
:type profile: dict
433+
:type port_profiles: dict of <port_id> : <port_profile>
434434
:raises: PortBindingFailed if any of the ports failed to be bound to
435435
the destination host
436436
:returns: dict, keyed by port ID, of a new host port

nova/network/neutronv2/api.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,7 +1299,7 @@ def supports_port_binding_extension(self, context):
12991299
return constants.PORT_BINDING_EXTENDED in self.extensions
13001300

13011301
def bind_ports_to_host(self, context, instance, host,
1302-
vnic_type=None, profile=None):
1302+
vnic_types=None, port_profiles=None):
13031303
"""Attempts to bind the ports from the instance on the given host
13041304
13051305
If the ports are already actively bound to another host, like the
@@ -1320,17 +1320,17 @@ def bind_ports_to_host(self, context, instance, host,
13201320
:param host: the host on which to bind the ports which
13211321
are attached to the instance
13221322
:type host: str
1323-
:param vnic_type: optional vnic type string for the host
1324-
port binding
1325-
:type vnic_type: str
1326-
:param profile: optional vif profile dict for the host port
1327-
binding; note that the port binding profile is mutable
1323+
:param vnic_types: optional dict for the host port binding
1324+
:type vnic_types: dict of <port_id> : <vnic_type>
1325+
:param port_profiles: optional dict per port ID for the host port
1326+
binding profile.
1327+
note that the port binding profile is mutable
13281328
via the networking "Port Binding" API so callers that
13291329
pass in a profile should ensure they have the latest
13301330
version from neutron with their changes merged,
13311331
which can be determined using the "revision_number"
13321332
attribute of the port.
1333-
:type profile: dict
1333+
:type port_profiles: dict of <port_id> : <port_profile>
13341334
:raises: PortBindingFailed if any of the ports failed to be bound to
13351335
the destination host
13361336
:returns: dict, keyed by port ID, of a new host port
@@ -1339,31 +1339,36 @@ def bind_ports_to_host(self, context, instance, host,
13391339
# Get the current ports off the instance. This assumes the cache is
13401340
# current.
13411341
network_info = instance.get_network_info()
1342-
port_ids = [vif['id'] for vif in network_info]
13431342

1344-
if not port_ids:
1343+
if not network_info:
13451344
# The instance doesn't have any ports so there is nothing to do.
13461345
LOG.debug('Instance does not have any ports.', instance=instance)
13471346
return {}
13481347

13491348
client = _get_ksa_client(context, admin=True)
13501349

1351-
# Now bind each port to the destination host and keep track of each
1352-
# port that is bound to the resulting binding so we can rollback in
1353-
# the event of a failure, or return the results if everything is OK.
1354-
binding = dict(host=host)
1355-
if vnic_type:
1356-
binding['vnic_type'] = vnic_type
1357-
if profile:
1358-
binding['profile'] = profile
1359-
data = dict(binding=binding)
1360-
13611350
# TODO(gibi): To support ports with resource request during server
13621351
# live migrate operation we need to take care of 'allocation' key in
13631352
# the binding profile per binding.
13641353

13651354
bindings_by_port_id = {}
1366-
for port_id in port_ids:
1355+
for vif in network_info:
1356+
# Now bind each port to the destination host and keep track of each
1357+
# port that is bound to the resulting binding so we can rollback in
1358+
# the event of a failure, or return the results if everything is OK
1359+
port_id = vif['id']
1360+
binding = dict(host=host)
1361+
if vnic_types is None or port_id not in vnic_types:
1362+
binding['vnic_type'] = vif['vnic_type']
1363+
else:
1364+
binding['vnic_type'] = vnic_types[port_id]
1365+
1366+
if port_profiles is None or port_id not in port_profiles:
1367+
binding['profile'] = vif['profile']
1368+
else:
1369+
binding['profile'] = port_profiles[port_id]
1370+
1371+
data = dict(binding=binding)
13671372
resp = self._create_port_binding(client, port_id, data)
13681373
if resp:
13691374
bindings_by_port_id[port_id] = resp.json()['binding']

nova/tests/unit/network/test_neutronv2.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6352,20 +6352,71 @@ def test_bind_ports_to_host_no_ports(self, mock_client):
63526352
@mock.patch('nova.network.neutronv2.api._get_ksa_client')
63536353
def test_bind_ports_to_host(self, mock_client):
63546354
"""Tests a single port happy path where everything is successful."""
6355+
def post_side_effect(*args, **kwargs):
6356+
self.assertDictEqual(binding, kwargs['json'])
6357+
return mock.DEFAULT
6358+
63556359
nwinfo = model.NetworkInfo([model.VIF(uuids.port)])
63566360
inst = objects.Instance(
63576361
info_cache=objects.InstanceInfoCache(network_info=nwinfo))
63586362
ctxt = context.get_context()
63596363
binding = {'binding': {'host': 'fake-host',
63606364
'vnic_type': 'normal',
63616365
'profile': {'foo': 'bar'}}}
6366+
63626367
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
63636368
mock_client.return_value.post.return_value = resp
6369+
mock_client.return_value.post.side_effect = post_side_effect
63646370
result = self.api.bind_ports_to_host(
6365-
ctxt, inst, 'fake-host', 'normal', {'foo': 'bar'})
6371+
ctxt, inst, 'fake-host', {uuids.port: 'normal'},
6372+
{uuids.port: {'foo': 'bar'}})
63666373
self.assertEqual(1, mock_client.return_value.post.call_count)
63676374
self.assertDictEqual({uuids.port: binding['binding']}, result)
63686375

6376+
@mock.patch('nova.network.neutronv2.api._get_ksa_client')
6377+
def test_bind_ports_to_host_with_vif_profile_and_vnic(self, mock_client):
6378+
"""Tests bind_ports_to_host with default/non-default parameters."""
6379+
def post_side_effect(*args, **kwargs):
6380+
self.assertDictEqual(binding, kwargs['json'])
6381+
return mock.DEFAULT
6382+
6383+
ctxt = context.get_context()
6384+
vif_profile = {'foo': 'default'}
6385+
nwinfo = model.NetworkInfo([model.VIF(id=uuids.port,
6386+
vnic_type="direct",
6387+
profile=vif_profile)])
6388+
inst = objects.Instance(
6389+
info_cache=objects.InstanceInfoCache(network_info=nwinfo))
6390+
binding = {'binding': {'host': 'fake-host',
6391+
'vnic_type': 'direct',
6392+
'profile': vif_profile}}
6393+
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
6394+
mock_client.return_value.post.return_value = resp
6395+
mock_client.return_value.post.side_effect = post_side_effect
6396+
result = self.api.bind_ports_to_host(ctxt, inst, 'fake-host')
6397+
self.assertEqual(1, mock_client.return_value.post.call_count)
6398+
self.assertDictEqual({uuids.port: binding['binding']}, result)
6399+
6400+
# assert that that if vnic_type and profile are set in VIF object
6401+
# the provided vnic_type and profile take precedence.
6402+
6403+
nwinfo = model.NetworkInfo([model.VIF(id=uuids.port,
6404+
vnic_type='direct',
6405+
profile=vif_profile)])
6406+
inst = objects.Instance(
6407+
info_cache=objects.InstanceInfoCache(network_info=nwinfo))
6408+
vif_profile_per_port = {uuids.port: {'foo': 'overridden'}}
6409+
vnic_type_per_port = {uuids.port: "direct-overridden"}
6410+
binding = {'binding': {'host': 'fake-host',
6411+
'vnic_type': 'direct-overridden',
6412+
'profile': {'foo': 'overridden'}}}
6413+
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
6414+
mock_client.return_value.post.return_value = resp
6415+
result = self.api.bind_ports_to_host(
6416+
ctxt, inst, 'fake-host', vnic_type_per_port, vif_profile_per_port)
6417+
self.assertEqual(2, mock_client.return_value.post.call_count)
6418+
self.assertDictEqual({uuids.port: binding['binding']}, result)
6419+
63696420
@mock.patch('nova.network.neutronv2.api._get_ksa_client')
63706421
def test_bind_ports_to_host_rollback(self, mock_client):
63716422
"""Tests a scenario where an instance has two ports, and binding the

0 commit comments

Comments
 (0)