Add multi-segment support

Neutron ML2 mechanisms allows multiple network segments. A net-show
call with a multi-segment setup won't return a single network-type
or segmentation id. Instead it returns a list of it with a certain
hierarchy.

Co-Authored-By: Daniel Gonzalez <daniel@gonzalez-nothnagel.de>
Change-Id: I3570752920d897c11cacb9e4a0b0d012ae2ce13a
Partially-Implements: bp manila-hpb-support
This commit is contained in:
Marc Koderer 2016-04-14 11:40:56 +02:00 committed by Ben Swartzlander
parent 345b8021ec
commit 8328ebde24
3 changed files with 311 additions and 12 deletions

View File

@ -29,6 +29,16 @@ from manila import utils
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
neutron_network_plugin_opts = [
cfg.StrOpt(
'neutron_physical_net_name',
help="The name of the physical network to determine which net segment "
"is used. This opt is optional and will only be used for "
"networks configured with multiple segments.",
default=None,
deprecated_group='DEFAULT'),
]
neutron_single_network_plugin_opts = [ neutron_single_network_plugin_opts = [
cfg.StrOpt( cfg.StrOpt(
'neutron_net_id', 'neutron_net_id',
@ -95,6 +105,9 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
self._neutron_api_args = args self._neutron_api_args = args
self._neutron_api_kwargs = kwargs self._neutron_api_kwargs = kwargs
self._label = kwargs.pop('label', 'user') self._label = kwargs.pop('label', 'user')
CONF.register_opts(
neutron_network_plugin_opts,
group=self.neutron_api.config_group_name)
@property @property
def label(self): def label(self):
@ -108,6 +121,10 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
**self._neutron_api_kwargs) **self._neutron_api_kwargs)
return self._neutron_api return self._neutron_api
def _store_neutron_net_info(self, context, share_network):
self._save_neutron_network_data(context, share_network)
self._save_neutron_subnet_data(context, share_network)
def allocate_network(self, context, share_server, share_network=None, def allocate_network(self, context, share_server, share_network=None,
**kwargs): **kwargs):
"""Allocate network resources using given network information. """Allocate network resources using given network information.
@ -128,8 +145,7 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
raise exception.NetworkBadConfigurationException(reason=msg) raise exception.NetworkBadConfigurationException(reason=msg)
self._verify_share_network(share_server['id'], share_network) self._verify_share_network(share_server['id'], share_network)
self._save_neutron_network_data(context, share_network) self._store_neutron_net_info(context, share_network)
self._save_neutron_subnet_data(context, share_network)
allocation_count = kwargs.get('count', 1) allocation_count = kwargs.get('count', 1)
device_owner = kwargs.get('device_owner', 'share') device_owner = kwargs.get('device_owner', 'share')
@ -181,8 +197,8 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
'mac_address': port['mac_address'], 'mac_address': port['mac_address'],
'status': constants.STATUS_ACTIVE, 'status': constants.STATUS_ACTIVE,
'label': self.label, 'label': self.label,
'network_type': share_network['network_type'], 'network_type': share_network.get('network_type'),
'segmentation_id': share_network['segmentation_id'], 'segmentation_id': share_network.get('segmentation_id'),
'ip_version': share_network['ip_version'], 'ip_version': share_network['ip_version'],
'cidr': share_network['cidr'], 'cidr': share_network['cidr'],
'mtu': share_network['mtu'], 'mtu': share_network['mtu'],
@ -203,13 +219,43 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
extensions = self.neutron_api.list_extensions() extensions = self.neutron_api.list_extensions()
return neutron_constants.PROVIDER_NW_EXT in extensions return neutron_constants.PROVIDER_NW_EXT in extensions
def _is_neutron_multi_segment(self, share_network, net_info=None):
if net_info is None:
net_info = self.neutron_api.get_network(
share_network['neutron_net_id'])
return 'segments' in net_info
def _save_neutron_network_data(self, context, share_network): def _save_neutron_network_data(self, context, share_network):
net_info = self.neutron_api.get_network( net_info = self.neutron_api.get_network(
share_network['neutron_net_id']) share_network['neutron_net_id'])
segmentation_id = None
network_type = None
if self._is_neutron_multi_segment(share_network, net_info):
# we have a multi segment network and need to identify the
# lowest segment used for binding
phy_nets = []
phy = self.neutron_api.configuration.neutron_physical_net_name
if not phy:
msg = "Cannot identify segment used for binding. Please add "
"neutron_physical_net_name in configuration."
raise exception.NetworkBadConfigurationException(reason=msg)
for segment in net_info['segments']:
phy_nets.append(segment['provider:physical_network'])
if segment['provider:physical_network'] == phy:
segmentation_id = segment['provider:segmentation_id']
network_type = segment['provider:network_type']
if not (segmentation_id and network_type):
msg = ("No matching neutron_physical_net_name found for %s "
"(found: %s)." % (phy, phy_nets))
raise exception.NetworkBadConfigurationException(reason=msg)
else:
network_type = net_info['provider:network_type']
segmentation_id = net_info['provider:segmentation_id']
provider_nw_dict = { provider_nw_dict = {
'network_type': net_info['provider:network_type'], 'network_type': network_type,
'segmentation_id': net_info['provider:segmentation_id'], 'segmentation_id': segmentation_id,
'mtu': net_info['mtu'], 'mtu': net_info['mtu'],
} }
share_network.update(provider_nw_dict) share_network.update(provider_nw_dict)
@ -377,6 +423,23 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin):
"local_link_information": local_links} "local_link_information": local_links}
return arguments return arguments
def _store_neutron_net_info(self, context, share_network):
"""Store the Neutron network info.
In case of dynamic multi segments the segment is determined while
binding the port. Therefore this method will return for multi segments
network without storing network information.
Instead, multi segments network will wait until ports are bound and
then store network information (see allocate_network()).
"""
if self._is_neutron_multi_segment(share_network):
# In case of dynamic multi segment the segment is determined while
# binding the port
return
super(NeutronBindNetworkPlugin, self)._store_neutron_net_info(
context, share_network)
def allocate_network(self, context, share_server, share_network=None, def allocate_network(self, context, share_server, share_network=None,
**kwargs): **kwargs):
ports = super(NeutronBindNetworkPlugin, self).allocate_network( ports = super(NeutronBindNetworkPlugin, self).allocate_network(
@ -389,6 +452,19 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin):
# order to update the ports with the correct binding. # order to update the ports with the correct binding.
if self.config.neutron_vnic_type != 'normal': if self.config.neutron_vnic_type != 'normal':
self._wait_for_ports_bind(ports, share_server) self._wait_for_ports_bind(ports, share_server)
if self._is_neutron_multi_segment(share_network):
# update segment information after port bind
super(NeutronBindNetworkPlugin, self)._store_neutron_net_info(
context, share_network)
for num, port in enumerate(ports):
port_info = {
'network_type': share_network['network_type'],
'segmentation_id': share_network['segmentation_id'],
'cidr': share_network['cidr'],
'ip_version': share_network['ip_version'],
}
ports[num] = self.db.network_allocation_update(
context, port['id'], port_info)
return ports return ports

View File

@ -73,8 +73,8 @@ fake_neutron_network = {
fake_share_network = { fake_share_network = {
'id': 'fake nw info id', 'id': 'fake nw info id',
'neutron_subnet_id': 'fake subnet id', 'neutron_subnet_id': fake_neutron_network['subnets'][0],
'neutron_net_id': 'fake net id', 'neutron_net_id': fake_neutron_network['id'],
'project_id': 'fake project id', 'project_id': 'fake project id',
'status': 'test_subnet_status', 'status': 'test_subnet_status',
'name': 'fake name', 'name': 'fake name',
@ -111,6 +111,77 @@ fake_network_allocation = {
'mtu': 1509, 'mtu': 1509,
} }
fake_nw_info = {
'segments': [
{
'provider:network_type': 'vlan',
'provider:physical_network': 'net1',
'provider:segmentation_id': 3926,
},
{
'provider:network_type': 'vxlan',
'provider:physical_network': None,
'provider:segmentation_id': 2000,
},
],
'mtu': 1509,
}
fake_neutron_network_multi = {
'admin_state_up': True,
'availability_zone_hints': [],
'availability_zones': ['nova'],
'description': '',
'id': 'fake net id',
'ipv4_address_scope': None,
'ipv6_address_scope': None,
'name': 'test_neutron_network',
'port_security_enabled': True,
'router:external': False,
'shared': False,
'status': 'ACTIVE',
'subnets': ['fake subnet id',
'fake subnet id 2'],
'segments': fake_nw_info['segments'],
'mtu': fake_nw_info['mtu'],
}
fake_share_network_multi = {
'id': 'fake nw info id',
'neutron_subnet_id': fake_neutron_network_multi['subnets'][0],
'neutron_net_id': fake_neutron_network_multi['id'],
'project_id': 'fake project id',
'status': 'test_subnet_status',
'name': 'fake name',
'description': 'fake description',
'security_services': [],
'ip_version': 4,
'cidr': 'fake_cidr',
'gateway': 'fake_gateway',
'mtu': fake_neutron_network_multi['mtu'],
}
fake_network_allocation_multi = {
'id': fake_neutron_port['id'],
'share_server_id': fake_share_server['id'],
'ip_address': fake_neutron_port['fixed_ips'][0]['ip_address'],
'mac_address': fake_neutron_port['mac_address'],
'status': constants.STATUS_ACTIVE,
'label': 'user',
'network_type': None,
'segmentation_id': None,
'ip_version': fake_share_network_multi['ip_version'],
'cidr': fake_share_network_multi['cidr'],
'gateway': 'fake_gateway',
'mtu': fake_share_network_multi['mtu'],
}
fake_binding_profile = {
'neutron_switch_id': 'fake switch id',
'neutron_port_id': 'fake port id',
'neutron_switch_info': 'fake switch info'
}
class NeutronNetworkPluginTest(test.TestCase): class NeutronNetworkPluginTest(test.TestCase):
@ -310,6 +381,58 @@ class NeutronNetworkPluginTest(test.TestCase):
fake_share_network['id'], fake_share_network['id'],
share_nw_update_dict) share_nw_update_dict)
@mock.patch.object(db_api, 'share_network_update', mock.Mock())
def test_save_neutron_network_data_multi_segment(self):
share_nw_update_dict = {
'network_type': 'vlan',
'segmentation_id': 3926,
'mtu': 1509
}
config_data = {
'DEFAULT': {
'neutron_physical_net_name': 'net1',
}
}
self.mock_object(self.plugin.neutron_api, 'get_network')
self.plugin.neutron_api.get_network.return_value = fake_nw_info
with test_utils.create_temp_config_with_opts(config_data):
self.plugin._save_neutron_network_data(self.fake_context,
fake_share_network)
self.plugin.neutron_api.get_network.assert_called_once_with(
fake_share_network['neutron_net_id'])
self.plugin.db.share_network_update.assert_called_once_with(
self.fake_context,
fake_share_network['id'],
share_nw_update_dict)
@mock.patch.object(db_api, 'share_network_update', mock.Mock())
def test_save_neutron_network_data_multi_segment_without_ident(self):
config_data = {
'DEFAULT': {
'neutron_physical_net_name': 'net100',
}
}
self.mock_object(self.plugin.neutron_api, 'get_network')
self.plugin.neutron_api.get_network.return_value = fake_nw_info
with test_utils.create_temp_config_with_opts(config_data):
self.assertRaises(exception.NetworkBadConfigurationException,
self.plugin._save_neutron_network_data,
self.fake_context, fake_share_network)
@mock.patch.object(db_api, 'share_network_update', mock.Mock())
def test_save_neutron_network_data_multi_segment_without_cfg(self):
self.mock_object(self.plugin.neutron_api, 'get_network')
self.plugin.neutron_api.get_network.return_value = fake_nw_info
self.assertRaises(exception.NetworkBadConfigurationException,
self.plugin._save_neutron_network_data,
self.fake_context, fake_share_network)
@mock.patch.object(db_api, 'share_network_update', mock.Mock()) @mock.patch.object(db_api, 'share_network_update', mock.Mock())
def test_save_neutron_subnet_data(self): def test_save_neutron_subnet_data(self):
neutron_subnet_info = { neutron_subnet_info = {
@ -543,6 +666,7 @@ class NeutronBindNetworkPluginTest(test.TestCase):
self.bind_plugin = self._get_neutron_network_plugin_instance() self.bind_plugin = self._get_neutron_network_plugin_instance()
self.bind_plugin.db = db_api self.bind_plugin.db = db_api
self.sleep_mock = self.mock_object(time, 'sleep') self.sleep_mock = self.mock_object(time, 'sleep')
self.fake_share_network_multi = dict(fake_share_network_multi)
def _get_neutron_network_plugin_instance(self, config_data=None): def _get_neutron_network_plugin_instance(self, config_data=None):
if config_data is None: if config_data is None:
@ -593,8 +717,6 @@ class NeutronBindNetworkPluginTest(test.TestCase):
[fake_neut_port1, fake_neut_port2], [fake_neut_port1, fake_neut_port2],
fake_share_server) fake_share_server)
@mock.patch.object(db_api, 'network_allocation_create',
mock.Mock(return_values=fake_network_allocation))
@mock.patch.object(db_api, 'share_network_get', @mock.patch.object(db_api, 'share_network_get',
mock.Mock(return_value=fake_share_network)) mock.Mock(return_value=fake_share_network))
@mock.patch.object(db_api, 'share_server_get', @mock.patch.object(db_api, 'share_server_get',
@ -611,6 +733,10 @@ class NeutronBindNetworkPluginTest(test.TestCase):
self.mock_object(neutron_host_id_opts, 'default') self.mock_object(neutron_host_id_opts, 'default')
neutron_host_id_opts.default = 'foohost1' neutron_host_id_opts.default = 'foohost1'
self.mock_object(db_api, 'network_allocation_create') self.mock_object(db_api, 'network_allocation_create')
db_api.network_allocation_create.return_value = fake_network_allocation
self.mock_object(self.bind_plugin.neutron_api, 'get_network')
self.bind_plugin.neutron_api.get_network.return_value = (
fake_neutron_network)
with mock.patch.object(self.bind_plugin.neutron_api, 'create_port', with mock.patch.object(self.bind_plugin.neutron_api, 'create_port',
mock.Mock(return_value=fake_neutron_port)): mock.Mock(return_value=fake_neutron_port)):
@ -641,6 +767,96 @@ class NeutronBindNetworkPluginTest(test.TestCase):
self.bind_plugin._wait_for_ports_bind.assert_called_once_with( self.bind_plugin._wait_for_ports_bind.assert_called_once_with(
[db_api.network_allocation_create()], fake_share_server) [db_api.network_allocation_create()], fake_share_server)
@mock.patch.object(db_api, 'network_allocation_create',
mock.Mock(return_values=fake_network_allocation_multi))
@mock.patch.object(db_api, 'share_network_get',
mock.Mock(return_value=fake_share_network_multi))
@mock.patch.object(db_api, 'share_server_get',
mock.Mock(return_value=fake_share_server))
def test_allocate_network_multi_segment(self):
network_allocation_update_data = {
'network_type':
fake_nw_info['segments'][0]['provider:network_type'],
'segmentation_id':
fake_nw_info['segments'][0]['provider:segmentation_id'],
}
network_update_data = dict(network_allocation_update_data)
network_update_data['mtu'] = fake_nw_info['mtu']
fake_network_allocation_multi_updated = dict(
fake_network_allocation_multi)
fake_network_allocation_multi_updated.update(
network_allocation_update_data)
fake_share_network_multi_updated = dict(fake_share_network_multi)
fake_share_network_multi_updated.update(network_update_data)
config_data = {
'DEFAULT': {
'neutron_net_id': 'fake net id',
'neutron_subnet_id': 'fake subnet id',
'neutron_physical_net_name': 'net1',
}
}
self.bind_plugin = self._get_neutron_network_plugin_instance(
config_data)
self.bind_plugin.db = db_api
self.mock_object(self.bind_plugin, '_has_provider_network_extension')
self.bind_plugin._has_provider_network_extension.return_value = True
save_subnet_data = self.mock_object(self.bind_plugin,
'_save_neutron_subnet_data')
self.mock_object(self.bind_plugin, '_wait_for_ports_bind')
neutron_host_id_opts = plugin.neutron_bind_network_plugin_opts[1]
self.mock_object(neutron_host_id_opts, 'default')
neutron_host_id_opts.default = 'foohost1'
self.mock_object(db_api, 'network_allocation_create')
db_api.network_allocation_create.return_value = (
fake_network_allocation_multi)
self.mock_object(db_api, 'network_allocation_update')
db_api.network_allocation_update.return_value = (
fake_network_allocation_multi_updated)
self.mock_object(self.bind_plugin.neutron_api, 'get_network')
self.bind_plugin.neutron_api.get_network.return_value = (
fake_neutron_network_multi)
self.mock_object(db_api, 'share_network_update')
with mock.patch.object(self.bind_plugin.neutron_api, 'create_port',
mock.Mock(return_value=fake_neutron_port)):
self.bind_plugin.allocate_network(
self.fake_context,
fake_share_server,
self.fake_share_network_multi,
allocation_info={'count': 1})
self.bind_plugin._has_provider_network_extension.assert_any_call()
save_subnet_data.assert_called_once_with(
self.fake_context,
fake_share_network_multi_updated)
expected_kwargs = {
'binding:vnic_type': 'baremetal',
'host_id': 'foohost1',
'network_id': fake_share_network_multi['neutron_net_id'],
'subnet_id': fake_share_network_multi['neutron_subnet_id'],
'device_owner': 'manila:share',
'device_id': fake_share_network_multi['id']
}
self.bind_plugin.neutron_api.create_port.assert_called_once_with(
fake_share_network_multi['project_id'], **expected_kwargs)
db_api.network_allocation_create.assert_called_once_with(
self.fake_context,
fake_network_allocation_multi)
db_api.share_network_update.assert_called_once_with(
self.fake_context,
fake_share_network_multi['id'],
network_update_data)
network_allocation_update_data['cidr'] = (
fake_share_network_multi['cidr'])
network_allocation_update_data['ip_version'] = (
fake_share_network_multi['ip_version'])
db_api.network_allocation_update.assert_called_once_with(
self.fake_context,
fake_neutron_port['id'],
network_allocation_update_data)
@ddt.data({ @ddt.data({
'neutron_binding_profiles': None, 'neutron_binding_profiles': None,
'binding_profiles': {} 'binding_profiles': {}
@ -821,6 +1037,7 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase):
'DEFAULT': { 'DEFAULT': {
'neutron_net_id': fake_net_id, 'neutron_net_id': fake_net_id,
'neutron_subnet_id': fake_subnet_id, 'neutron_subnet_id': fake_subnet_id,
'neutron_physical_net_name': 'net1',
} }
} }
fake_net = {'subnets': ['fake1', 'fake2', fake_subnet_id]} fake_net = {'subnets': ['fake1', 'fake2', fake_subnet_id]}
@ -836,8 +1053,8 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase):
'port1', 'port2'] 'port1', 'port2']
instance = self._get_neutron_network_plugin_instance() instance = self._get_neutron_network_plugin_instance()
share_server = 'fake_share_server' share_server = 'fake_share_server'
share_network = 'fake_share_network' share_network = {'neutron_net_id': {}}
share_network_upd = 'updated_fake_share_network' share_network_upd = {'neutron_net_id': {'upd': True}}
count = 2 count = 2
device_owner = 'fake_device_owner' device_owner = 'fake_device_owner'
self.mock_object( self.mock_object(
@ -1279,6 +1496,9 @@ class NeutronBindNetworkPluginWithNormalTypeTest(test.TestCase):
self.mock_object(neutron_host_id_opts, 'default') self.mock_object(neutron_host_id_opts, 'default')
neutron_host_id_opts.default = 'foohost1' neutron_host_id_opts.default = 'foohost1'
self.mock_object(db_api, 'network_allocation_create') self.mock_object(db_api, 'network_allocation_create')
multi_seg = self.mock_object(
self.bind_plugin, '_is_neutron_multi_segment')
multi_seg.return_value = False
with mock.patch.object(self.bind_plugin.neutron_api, 'create_port', with mock.patch.object(self.bind_plugin.neutron_api, 'create_port',
mock.Mock(return_value=fake_neutron_port)): mock.Mock(return_value=fake_neutron_port)):

View File

@ -0,0 +1,3 @@
---
features:
- Added port binding support for neutron networks with multiple segments.