Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions nova/network/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
VIF_TYPE_BINDING_FAILED = 'binding_failed'
VIF_TYPE_VIF = 'vif'
VIF_TYPE_UNBOUND = 'unbound'
VIF_TYPE_TRUNK_SUBPORT = 'trunk-subport'


# Constants for dictionary keys in the 'vif_details' field in the VIF
Expand Down Expand Up @@ -411,7 +412,7 @@ def __init__(self, id=None, address=None, network=None, type=None,
qbh_params=None, qbg_params=None, active=False,
vnic_type=VNIC_TYPE_NORMAL, profile=None,
preserve_on_delete=False, delegate_create=False,
**kwargs):
trunk_vifs=None, **kwargs):
super(VIF, self).__init__()

self['id'] = id
Expand All @@ -429,14 +430,16 @@ def __init__(self, id=None, address=None, network=None, type=None,
self['profile'] = profile
self['preserve_on_delete'] = preserve_on_delete
self['delegate_create'] = delegate_create
self['trunk_vifs'] = trunk_vifs or []

self._set_meta(kwargs)

def __eq__(self, other):
keys = ['id', 'address', 'network', 'vnic_type',
'type', 'profile', 'details', 'devname',
'ovs_interfaceid', 'qbh_params', 'qbg_params',
'active', 'preserve_on_delete', 'delegate_create']
'active', 'preserve_on_delete', 'delegate_create',
'trunk_vifs']
return all(self[k] == other[k] for k in keys)

def __ne__(self, other):
Expand Down Expand Up @@ -507,9 +510,16 @@ def get_physical_network(self):
phy_network = self['details'].get(VIF_DETAILS_PHYSICAL_NETWORK)
return phy_network

def add_trunk_vif(self, vif):
if any(vif['id'] == _vif['id'] for _vif in self['trunk_vifs']):
return
self['trunk_vifs'].append(vif)

@classmethod
def hydrate(cls, vif):
vif = cls(**vif)
vif['trunk_vifs'] = [VIF.hydrate(trunk_vif) for trunk_vif
in vif.get('trunk_vifs', [])]
vif['network'] = Network.hydrate(vif['network'])
return vif

Expand Down
37 changes: 35 additions & 2 deletions nova/network/neutron.py
Original file line number Diff line number Diff line change
Expand Up @@ -3385,13 +3385,17 @@ def _build_vif_model(self, context, client, current_neutron_port,
preserve_on_delete = (current_neutron_port['id'] in
preexisting_port_ids)

return network_model.VIF(
vif_type = current_neutron_port.get('binding:vif_type')
if current_neutron_port.get('device_owner') == 'trunk:subport':
vif_type = network_model.VIF_TYPE_TRUNK_SUBPORT

vif = network_model.VIF(
id=current_neutron_port['id'],
address=current_neutron_port['mac_address'],
network=network,
vnic_type=current_neutron_port.get('binding:vnic_type',
network_model.VNIC_TYPE_NORMAL),
type=current_neutron_port.get('binding:vif_type'),
type=vif_type,
profile=get_binding_profile(current_neutron_port),
details=current_neutron_port.get('binding:vif_details'),
ovs_interfaceid=ovs_interfaceid,
Expand All @@ -3400,6 +3404,8 @@ def _build_vif_model(self, context, client, current_neutron_port,
preserve_on_delete=preserve_on_delete,
delegate_create=True,
)
self._populate_trunk_info(context, client, vif, current_neutron_port)
return vif

def _log_error_if_vnic_type_changed(
self, port_id, old_vnic_type, new_vnic_type, instance
Expand All @@ -3420,6 +3426,33 @@ def _log_error_if_vnic_type_changed(
instance=instance
)

def _populate_trunk_info(self, context, client, vif, current_neutron_port):
sub_ports = current_neutron_port.get('trunk_details', {}).get(
'sub_ports') or []
if not sub_ports:
return

port_ids = [sp['port_id'] for sp in sub_ports]
ports = client.list_ports(id=port_ids).get('ports', [])
ports_by_id = {p['id']: p for p in ports}

net_ids = list({p['network_id'] for p in ports})
networks = client.list_networks(id=net_ids).get('networks', [])

seg_by_port = {sp['port_id']: sp.get('segmentation_id')
for sp in sub_ports}

for port_id in port_ids:
port = ports_by_id.get(port_id)
if not port:
continue
subport_vif = self._build_vif_model(
context, client, port, networks, [port_id])
subport_profile = dict(subport_vif['profile'] or {})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to convert to a dict() here? What could subport_vif['profile'] be that we'll need this, a list of tuples?

subport_profile['tag'] = seg_by_port.get(port_id)
subport_vif['profile'] = subport_profile
vif.add_trunk_vif(subport_vif)

def _build_network_info_model(self, context, instance, networks=None,
port_ids=None, admin_client=None,
preexisting_port_ids=None,
Expand Down
39 changes: 39 additions & 0 deletions nova/tests/unit/network/test_network_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,20 @@ def test_hydrate_vif_with_type(self):
self.assertEqual(fake_network_cache_model.new_network(),
vif['network'])

def test_hydrate_vif_with_trunk_vifs(self):
subport_vif = fake_network_cache_model.new_vif(
{'id': 'subport1', 'type': 'trunk-subport',
'profile': {'tag': 1049}})
parent_vif = fake_network_cache_model.new_vif(
{'id': 'parent1', 'trunk_vifs': [subport_vif]})

hydrated = model.VIF.hydrate(parent_vif)

self.assertEqual(1, len(hydrated['trunk_vifs']))
self.assertIsInstance(hydrated['trunk_vifs'][0], model.VIF)
self.assertEqual('subport1', hydrated['trunk_vifs'][0]['id'])
self.assertEqual(1049, hydrated['trunk_vifs'][0]['profile']['tag'])


class NetworkInfoTests(test.NoDBTestCase):
def test_create_model(self):
Expand Down Expand Up @@ -1081,6 +1095,31 @@ def test_get_network_metadata_json_ipv6_addr_mode_stateful(self):
def test_get_network_metadata_json_ipv6_addr_mode_stateless(self):
self._test_get_network_metadata_json_ipv6_addr_mode('dhcpv6-stateless')

def test_get_network_metadata_json_trunks(self):
subport_vif = fake_network_cache_model.new_vif(
{'type': 'trunk-subport', 'devname': 'tapsubport1',
'id': 'subport1',
'profile': {'tag': 1049}})
parent_vif = fake_network_cache_model.new_vif(
{'type': 'ovs', 'devname': 'tapparent1',
'id': 'parent1',
'trunk_vifs': [subport_vif]})

netinfo = model.NetworkInfo([parent_vif])

net_metadata = netutils.get_network_metadata(netinfo)

self.assertIn({
'id': 'tapsubport1',
'vif_id': 'subport1',
'type': 'vlan',
'mtu': None,
'ethernet_mac_address': 'aa:aa:aa:aa:aa:aa',
'vlan_link': 'tapparent1',
'vlan_id': 1049,
'vlan_mac_address': 'aa:aa:aa:aa:aa:aa'},
net_metadata['links'])

def test__get_nets(self):
expected_net = {
'id': 'network0',
Expand Down
120 changes: 120 additions & 0 deletions nova/tests/unit/network/test_neutron.py
Original file line number Diff line number Diff line change
Expand Up @@ -3331,6 +3331,126 @@ def test_build_network_info_model(self, mock_get_client,
mock_get_physnet.assert_has_calls([
mock.call(self.context, mocked_client, 'net-id')] * 6)

@mock.patch.object(neutronapi.API, '_get_physnet_tunneled_info',
return_value=(None, False))
@mock.patch.object(neutronapi.API, '_get_preexisting_port_ids',
return_value=['port5'])
@mock.patch.object(neutronapi.API, '_get_subnets_from_port',
return_value=[model.Subnet(cidr='1.0.0.0/8')])
@mock.patch.object(neutronapi.API, '_get_floating_ips_by_fixed_and_port',
return_value=[{'floating_ip_address': '10.0.0.1'}])
@mock.patch.object(neutronapi, 'get_client')
def test_build_network_info_model_trunk(
self, mock_get_client, mock_get_floating, mock_get_subnets,
mock_get_preexisting, mock_get_physnet):
mocked_client = mock.create_autospec(client.Client)
mock_get_client.return_value = mocked_client
fake_inst = objects.Instance()
fake_inst.project_id = uuids.fake
fake_inst.uuid = uuids.instance
fake_inst.info_cache = objects.InstanceInfoCache()
fake_inst.info_cache.network_info = model.NetworkInfo()
fake_ports = [
{'id': 'port1',
'network_id': 'net-id',
'admin_state_up': True,
'status': 'ACTIVE',
'tenant_id': uuids.fake,
'fixed_ips': [{'ip_address': '1.1.1.1'}],
'mac_address': 'de:ad:be:ef:00:05',
'binding:vif_type': model.VIF_TYPE_802_QBH,
'binding:vnic_type': model.VNIC_TYPE_MACVTAP,
constants.BINDING_PROFILE: {'pci_vendor_info': '1137:0047',
'pci_slot': '0000:0a:00.2',
'physical_network': 'physnet1'},
'binding:vif_details': {model.VIF_DETAILS_PROFILEID: 'pfid'},
'trunk_details': {'sub_ports': [{
'segmentation_id': 1049,
'segmentation_type': 'vlan',
'port_id': 'subport1'}
]},
},
]
fake_subport = {
'id': 'subport1',
'network_id': 'net-id2',
'admin_state_up': True,
'status': 'ACTIVE',
'fixed_ips': [{'ip_address': '1.1.2.1'}],
'mac_address': 'aa:bb:cc:dd:ee:ff',
'binding:vif_type': model.VIF_TYPE_BRIDGE,
'binding:vnic_type': model.VNIC_TYPE_NORMAL,
'binding:vif_details': {},
'tenant_id': uuids.fake,
}
fake_nets = [
{'id': 'net-id',
'name': 'foo',
'tenant_id': uuids.fake,
}
]
fake_subport_net = {
'id': 'net-id2',
'name': 'subport-net',
'tenant_id': uuids.fake,
}

def list_ports(**kwargs):
if kwargs.get('id') == ['subport1']:
return {'ports': [fake_subport]}
return {'ports': fake_ports}

mocked_client.list_ports.side_effect = list_ports
mocked_client.list_networks.return_value = {
'networks': [fake_subport_net]}

fake_inst.info_cache = objects.InstanceInfoCache.new(
self.context, uuids.instance)
fake_inst.info_cache.network_info = model.NetworkInfo.hydrate([])

nw_infos = self.api._build_network_info_model(
self.context, fake_inst,
fake_nets,
[fake_ports[0]['id']],
preexisting_port_ids=[])

mocked_client.list_ports.assert_any_call(
tenant_id=uuids.fake, device_id=uuids.instance)
mocked_client.list_ports.assert_any_call(id=['subport1'])
mocked_client.list_networks.assert_called_once_with(id=['net-id2'])
self.assertIn('trunk_vifs', nw_infos[0])
self.assertEqual(1, len(nw_infos[0]['trunk_vifs']))
subport_vif = nw_infos[0]['trunk_vifs'][0]
self.assertEqual('subport1', subport_vif['id'])
self.assertEqual(1049, subport_vif['profile']['tag'])

@mock.patch.object(neutronapi.API, '_build_vif_model')
def test_populate_trunk_info_injects_tag(self, mock_build_vif):
parent_vif = model.VIF(id='parent-port-id')
subport_vif = model.VIF(id='subport-port-id', profile={'foo': 'bar'})
mock_build_vif.return_value = subport_vif

mocked_client = mock.create_autospec(client.Client)
mocked_client.list_ports.return_value = {'ports': [
{'id': 'subport-port-id', 'network_id': 'net-id'}]}
mocked_client.list_networks.return_value = {
'networks': [{'id': 'net-id'}]}

current_neutron_port = {
'trunk_details': {'sub_ports': [{
'port_id': 'subport-port-id',
'segmentation_id': 1049,
'segmentation_type': 'vlan'}]}}

self.api._populate_trunk_info(
self.context, mocked_client, parent_vif, current_neutron_port)

self.assertEqual(1, len(parent_vif['trunk_vifs']))
child = parent_vif['trunk_vifs'][0]
self.assertEqual('subport-port-id', child['id'])
self.assertEqual(1049, child['profile']['tag'])
self.assertEqual('bar', child['profile']['foo'])

@mock.patch.object(neutronapi, 'get_client')
@mock.patch('nova.network.neutron.API._nw_info_get_subnets')
@mock.patch('nova.network.neutron.API._nw_info_get_ips')
Expand Down
33 changes: 33 additions & 0 deletions nova/tests/unit/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit import fake_block_device
from nova.tests.unit import fake_network
from nova.tests.unit import fake_network_cache_model
from nova.tests.unit import fake_requests
from nova import utils
from nova.virt import netutils
Expand Down Expand Up @@ -1068,6 +1069,38 @@ def test_network_data_response(self):
for k, v in nw_data.items():
self.assertEqual(nw[k], v)

def test_network_data_response_with_trunk_subport(self):
inst = self.instance.obj_clone()

subport_vif = fake_network_cache_model.new_vif(
{'type': 'trunk-subport', 'devname': 'tapsubport',
'id': 'subport-id', 'address': 'aa:aa:aa:aa:aa:bb',
'profile': {'tag': 1049}})
parent_vif = fake_network_cache_model.new_vif(
{'type': 'ovs', 'devname': 'tapparent',
'id': 'parent-id',
'trunk_vifs': [subport_vif]})
nw_info = network_model.NetworkInfo([parent_vif])

mdinst = fake_InstanceMetadata(self, inst, network_info=nw_info)

nwpath = "/openstack/2015-10-15/network_data.json"
nw = jsonutils.loads(mdinst.lookup(nwpath))

vlan_links = [link for link in nw['links']
if link.get('type') == 'vlan']
self.assertEqual(1, len(vlan_links))
self.assertEqual({
'id': 'tapsubport',
'vif_id': 'subport-id',
'type': 'vlan',
'mtu': None,
'ethernet_mac_address': 'aa:aa:aa:aa:aa:bb',
'vlan_link': 'tapparent',
'vlan_id': 1049,
'vlan_mac_address': 'aa:aa:aa:aa:aa:bb'},
vlan_links[0])


class MetadataHandlerTestCase(test.TestCase):
"""Test that metadata is returning proper values."""
Expand Down
18 changes: 16 additions & 2 deletions nova/virt/netutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,13 @@ def get_network_metadata(network_info):
ifc_num = -1
net_num = -1

vifs_with_parent = []
for vif in network_info:
vifs_with_parent.append((vif, None))
for subport in vif['trunk_vifs']:
vifs_with_parent.append((subport, vif))

for vif, parent_vif in vifs_with_parent:
if not vif.get('network') or not vif['network'].get('subnets'):
continue

Expand All @@ -202,7 +208,7 @@ def get_network_metadata(network_info):

# Get the VIF or physical NIC data
if subnet_v4 or subnet_v6:
link = _get_eth_link(vif, ifc_num)
link = _get_eth_link(vif, ifc_num, parent_vif)
links.append(link)

# Add IPv4 and IPv6 networks if they exist
Expand Down Expand Up @@ -240,7 +246,7 @@ def get_ec2_ip_info(network_info):
return ip_info


def _get_eth_link(vif, ifc_num):
def _get_eth_link(vif, ifc_num, parent_vif=None):
"""Get a VIF or physical NIC representation.

:param vif: Neutron VIF
Expand All @@ -256,6 +262,8 @@ def _get_eth_link(vif, ifc_num):
# Use 'phy' for physical links. Ethernet can be confusing
if vif.get('type') in model.LEGACY_EXPOSED_VIF_TYPES:
nic_type = vif.get('type')
elif vif.get('type') == model.VIF_TYPE_TRUNK_SUBPORT:
nic_type = 'vlan'
else:
nic_type = 'phy'

Expand All @@ -266,6 +274,12 @@ def _get_eth_link(vif, ifc_num):
'mtu': _get_link_mtu(vif),
'ethernet_mac_address': vif.get('address'),
}

if nic_type == 'vlan':
link['vlan_link'] = parent_vif['devname']
link['vlan_id'] = vif['profile']['tag']
link['vlan_mac_address'] = vif['address']

return link


Expand Down