Merge "Allow per-port modification of vnic_type and profile"

This commit is contained in:
Zuul
2019-03-10 15:06:30 +00:00
committed by Gerrit Code Review
3 changed files with 85 additions and 29 deletions

View File

@@ -399,7 +399,7 @@ class NetworkAPI(base.Base):
return False
def bind_ports_to_host(self, context, instance, host,
vnic_type=None, profile=None):
vnic_types=None, port_profiles=None):
"""Attempts to bind the ports from the instance on the given host
If the ports are already actively bound to another host, like the
@@ -420,17 +420,17 @@ class NetworkAPI(base.Base):
:param host: the host on which to bind the ports which
are attached to the instance
:type host: str
:param vnic_type: optional vnic type string for the host
port binding
:type vnic_type: str
:param profile: optional vif profile dict for the host port
binding; note that the port binding profile is mutable
:param vnic_types: optional dict for the host port binding
:type vnic_types: dict of <port_id> : <vnic_type>
:param port_profiles: optional dict per port ID for the host port
binding profile.
note that the port binding profile is mutable
via the networking "Port Binding" API so callers that
pass in a profile should ensure they have the latest
version from neutron with their changes merged,
which can be determined using the "revision_number"
attribute of the port.
:type profile: dict
:type port_profiles: dict of <port_id> : <port_profile>
:raises: PortBindingFailed if any of the ports failed to be bound to
the destination host
:returns: dict, keyed by port ID, of a new host port

View File

@@ -1299,7 +1299,7 @@ class API(base_api.NetworkAPI):
return constants.PORT_BINDING_EXTENDED in self.extensions
def bind_ports_to_host(self, context, instance, host,
vnic_type=None, profile=None):
vnic_types=None, port_profiles=None):
"""Attempts to bind the ports from the instance on the given host
If the ports are already actively bound to another host, like the
@@ -1320,17 +1320,17 @@ class API(base_api.NetworkAPI):
:param host: the host on which to bind the ports which
are attached to the instance
:type host: str
:param vnic_type: optional vnic type string for the host
port binding
:type vnic_type: str
:param profile: optional vif profile dict for the host port
binding; note that the port binding profile is mutable
:param vnic_types: optional dict for the host port binding
:type vnic_types: dict of <port_id> : <vnic_type>
:param port_profiles: optional dict per port ID for the host port
binding profile.
note that the port binding profile is mutable
via the networking "Port Binding" API so callers that
pass in a profile should ensure they have the latest
version from neutron with their changes merged,
which can be determined using the "revision_number"
attribute of the port.
:type profile: dict
:type port_profiles: dict of <port_id> : <port_profile>
:raises: PortBindingFailed if any of the ports failed to be bound to
the destination host
:returns: dict, keyed by port ID, of a new host port
@@ -1339,31 +1339,36 @@ class API(base_api.NetworkAPI):
# Get the current ports off the instance. This assumes the cache is
# current.
network_info = instance.get_network_info()
port_ids = [vif['id'] for vif in network_info]
if not port_ids:
if not network_info:
# The instance doesn't have any ports so there is nothing to do.
LOG.debug('Instance does not have any ports.', instance=instance)
return {}
client = _get_ksa_client(context, admin=True)
# Now bind each port to the destination host and keep track of each
# port that is bound to the resulting binding so we can rollback in
# the event of a failure, or return the results if everything is OK.
binding = dict(host=host)
if vnic_type:
binding['vnic_type'] = vnic_type
if profile:
binding['profile'] = profile
data = dict(binding=binding)
# TODO(gibi): To support ports with resource request during server
# live migrate operation we need to take care of 'allocation' key in
# the binding profile per binding.
bindings_by_port_id = {}
for port_id in port_ids:
for vif in network_info:
# Now bind each port to the destination host and keep track of each
# port that is bound to the resulting binding so we can rollback in
# the event of a failure, or return the results if everything is OK
port_id = vif['id']
binding = dict(host=host)
if vnic_types is None or port_id not in vnic_types:
binding['vnic_type'] = vif['vnic_type']
else:
binding['vnic_type'] = vnic_types[port_id]
if port_profiles is None or port_id not in port_profiles:
binding['profile'] = vif['profile']
else:
binding['profile'] = port_profiles[port_id]
data = dict(binding=binding)
resp = self._create_port_binding(client, port_id, data)
if resp:
bindings_by_port_id[port_id] = resp.json()['binding']

View File

@@ -6352,6 +6352,10 @@ class TestPortBindingWithMock(test.NoDBTestCase):
@mock.patch('nova.network.neutronv2.api._get_ksa_client')
def test_bind_ports_to_host(self, mock_client):
"""Tests a single port happy path where everything is successful."""
def post_side_effect(*args, **kwargs):
self.assertDictEqual(binding, kwargs['json'])
return mock.DEFAULT
nwinfo = model.NetworkInfo([model.VIF(uuids.port)])
inst = objects.Instance(
info_cache=objects.InstanceInfoCache(network_info=nwinfo))
@@ -6359,11 +6363,58 @@ class TestPortBindingWithMock(test.NoDBTestCase):
binding = {'binding': {'host': 'fake-host',
'vnic_type': 'normal',
'profile': {'foo': 'bar'}}}
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
mock_client.return_value.post.return_value = resp
mock_client.return_value.post.side_effect = post_side_effect
result = self.api.bind_ports_to_host(
ctxt, inst, 'fake-host', {uuids.port: 'normal'},
{uuids.port: {'foo': 'bar'}})
self.assertEqual(1, mock_client.return_value.post.call_count)
self.assertDictEqual({uuids.port: binding['binding']}, result)
@mock.patch('nova.network.neutronv2.api._get_ksa_client')
def test_bind_ports_to_host_with_vif_profile_and_vnic(self, mock_client):
"""Tests bind_ports_to_host with default/non-default parameters."""
def post_side_effect(*args, **kwargs):
self.assertDictEqual(binding, kwargs['json'])
return mock.DEFAULT
ctxt = context.get_context()
vif_profile = {'foo': 'default'}
nwinfo = model.NetworkInfo([model.VIF(id=uuids.port,
vnic_type="direct",
profile=vif_profile)])
inst = objects.Instance(
info_cache=objects.InstanceInfoCache(network_info=nwinfo))
binding = {'binding': {'host': 'fake-host',
'vnic_type': 'direct',
'profile': vif_profile}}
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
mock_client.return_value.post.return_value = resp
mock_client.return_value.post.side_effect = post_side_effect
result = self.api.bind_ports_to_host(ctxt, inst, 'fake-host')
self.assertEqual(1, mock_client.return_value.post.call_count)
self.assertDictEqual({uuids.port: binding['binding']}, result)
# assert that that if vnic_type and profile are set in VIF object
# the provided vnic_type and profile take precedence.
nwinfo = model.NetworkInfo([model.VIF(id=uuids.port,
vnic_type='direct',
profile=vif_profile)])
inst = objects.Instance(
info_cache=objects.InstanceInfoCache(network_info=nwinfo))
vif_profile_per_port = {uuids.port: {'foo': 'overridden'}}
vnic_type_per_port = {uuids.port: "direct-overridden"}
binding = {'binding': {'host': 'fake-host',
'vnic_type': 'direct-overridden',
'profile': {'foo': 'overridden'}}}
resp = fake_req.FakeResponse(200, content=jsonutils.dumps(binding))
mock_client.return_value.post.return_value = resp
result = self.api.bind_ports_to_host(
ctxt, inst, 'fake-host', 'normal', {'foo': 'bar'})
self.assertEqual(1, mock_client.return_value.post.call_count)
ctxt, inst, 'fake-host', vnic_type_per_port, vif_profile_per_port)
self.assertEqual(2, mock_client.return_value.post.call_count)
self.assertDictEqual({uuids.port: binding['binding']}, result)
@mock.patch('nova.network.neutronv2.api._get_ksa_client')