Add MTU information in DB and API

MTU value can be different for each neutron network.
E.g. for high-performance use-cases it's very important to also
support MTU's with jumbo frames.

This patch exposes this information to the drivers. Each driver
should setup its resources accordingly.

The tempest test actually just covers the api change. Better coverage will
be added when the container driver lands.

APIImpact
DocImpact

Change-Id: I9b4efae620ec9f6790547c8fffc58872d43277f5
Implements: bp add-network-mtu
Related-Bug: #1612528
This commit is contained in:
Marc Koderer 2016-08-04 15:01:29 +02:00
parent 4a9a07ea9f
commit e3afcd751f
16 changed files with 333 additions and 21 deletions

View File

@ -71,6 +71,7 @@ REST_API_VERSION_HISTORY = """
* 2.18 - Add gateway to the JSON response of share network show API.
* 2.19 - Share snapshot instances admin APIs
(list/show/detail/reset-status).
* 2.20 - Add MTU to the JSON response of share network show API.
"""
@ -78,7 +79,7 @@ REST_API_VERSION_HISTORY = """
# The default api version request is defined to be the
# the minimum version of the API supported.
_MIN_API_VERSION = "2.0"
_MAX_API_VERSION = "2.19"
_MAX_API_VERSION = "2.20"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -122,3 +122,7 @@ user documentation.
2.19
----
Add admin APIs(list/show/detail/reset-status) of snapshot instances.
2.20
----
Add MTU in share network show API.

View File

@ -20,7 +20,7 @@ class ViewBuilder(common.ViewBuilder):
"""Model a server API response as a python dictionary."""
_collection_name = 'share_networks'
_detail_version_modifiers = ["add_gateway"]
_detail_version_modifiers = ["add_gateway", "add_mtu"]
def build_share_network(self, request, share_network):
"""View of a share network."""
@ -61,3 +61,7 @@ class ViewBuilder(common.ViewBuilder):
@common.ViewBuilder.versioned_method("2.18")
def add_gateway(self, context, network_dict, network):
network_dict['gateway'] = network.get('gateway')
@common.ViewBuilder.versioned_method("2.20")
def add_mtu(self, context, network_dict, network):
network_dict['mtu'] = network.get('mtu')

View File

@ -0,0 +1,40 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""add_mtu_network_allocations
Revision ID: 493eaffd79e1
Revises: e8ea58723178
Create Date: 2016-08-01 14:18:31.899606
"""
# revision identifiers, used by Alembic.
revision = '493eaffd79e1'
down_revision = 'e8ea58723178'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column(
'network_allocations',
sa.Column('mtu', sa.Integer, nullable=True))
op.add_column(
'share_networks',
sa.Column('mtu', sa.Integer, nullable=True))
def downgrade():
op.drop_column('network_allocations', 'mtu')
op.drop_column('share_networks', 'mtu')

View File

@ -755,6 +755,7 @@ class ShareNetwork(BASE, ManilaBase):
segmentation_id = Column(Integer, nullable=True)
cidr = Column(String(64), nullable=True)
gateway = Column(String(64), nullable=True)
mtu = Column(Integer, nullable=True)
ip_version = Column(Integer, nullable=True)
name = Column(String(255), nullable=True)
description = Column(String(255), nullable=True)
@ -864,6 +865,7 @@ class NetworkAllocation(BASE, ManilaBase):
ip_version = Column(Integer, nullable=True)
cidr = Column(String(64), nullable=True)
gateway = Column(String(64), nullable=True)
mtu = Column(Integer, nullable=True)
network_type = Column(String(32), nullable=True)
segmentation_id = Column(Integer, nullable=True)
mac_address = Column(String(32), nullable=True)

View File

@ -131,6 +131,7 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
'segmentation_id': share_network['segmentation_id'],
'ip_version': share_network['ip_version'],
'cidr': share_network['cidr'],
'mtu': share_network['mtu'],
}
return self.db.network_allocation_create(context, port_dict)
@ -154,7 +155,8 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
provider_nw_dict = {
'network_type': net_info['provider:network_type'],
'segmentation_id': net_info['provider:segmentation_id']
'segmentation_id': net_info['provider:segmentation_id'],
'mtu': net_info['mtu'],
}
share_network.update(provider_nw_dict)

View File

@ -92,6 +92,7 @@ class NovaNetworkPlugin(network.NetworkBaseAPI):
'ip_version': share_network['ip_version'],
'segmentation_id': share_network['segmentation_id'],
'network_type': share_network['network_type'],
'mtu': share_network['mtu'],
}
self.nova_api.fixed_ip_reserve(self.admin_context, ip_address)
allocations.append(
@ -155,6 +156,7 @@ class NovaNetworkPlugin(network.NetworkBaseAPI):
'ip_version': (4 if nova_net['cidr'] else 6),
'segmentation_id': nova_net['vlan'],
'network_type': ('vlan' if nova_net['vlan'] else 'flat'),
'mtu': nova_net['mtu'],
}
share_network.update(data)
if self.label != 'admin':

View File

@ -65,6 +65,12 @@ standalone_network_plugin_opts = [
help="IP version of network. Optional."
"Allowed values are '4' and '6'. Default value is '4'.",
deprecated_group='DEFAULT'),
cfg.IntOpt(
'standalone_network_plugin_mtu',
default=1500,
help="Maximum Transmission Unit (MTU) value of the network. Default "
"value is 1500.",
deprecated_group='DEFAULT'),
]
CONF = cfg.CONF
@ -135,6 +141,7 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI):
six.text_type(self.net.network),
self.gateway,
six.text_type(self.net.broadcast))
self.mtu = self.configuration.standalone_network_plugin_mtu
def _get_network(self):
"""Returns IPNetwork object calculated from gateway and netmask."""
@ -251,6 +258,7 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI):
'cidr': six.text_type(self.net.cidr),
'gateway': six.text_type(self.gateway),
'ip_version': self.ip_version,
'mtu': self.mtu,
}
share_network.update(data)
if self.label != 'admin':
@ -284,6 +292,7 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI):
'cidr': share_network['cidr'],
'gateway': share_network['gateway'],
'ip_version': share_network['ip_version'],
'mtu': share_network['mtu'],
}
allocations.append(
self.db.network_allocation_create(context, data))

View File

@ -26,7 +26,6 @@ class ViewBuilderTestCase(test.TestCase):
def setUp(self):
super(ViewBuilderTestCase, self).setUp()
self.builder = share_networks.ViewBuilder()
self.req = fakes.HTTPRequest.blank('/share-networks', version="2.18")
def test__collection_name(self):
self.assertEqual('share_networks', self.builder._collection_name)
@ -36,13 +35,14 @@ class ViewBuilderTestCase(test.TestCase):
{'id': 'fake_sn_id', 'name': 'fake_sn_name', 'fake_extra_key': 'foo'},
)
def test_build_share_network_v_2_18(self, sn):
req = fakes.HTTPRequest.blank('/share-networks', version="2.18")
expected_keys = (
'id', 'name', 'project_id', 'created_at', 'updated_at',
'neutron_net_id', 'neutron_subnet_id', 'nova_net_id',
'network_type', 'segmentation_id', 'cidr', 'ip_version',
'gateway', 'description')
result = self.builder.build_share_network(self.req, sn)
result = self.builder.build_share_network(req, sn)
self.assertEqual(1, len(result))
self.assertIn('share_network', result)
@ -71,6 +71,7 @@ class ViewBuilderTestCase(test.TestCase):
dict(id='fake_id2', name='fake_name2')],
)
def test_build_share_networks_with_details_v_2_18(self, share_networks):
req = fakes.HTTPRequest.blank('/share-networks', version="2.18")
expected = []
for share_network in share_networks:
expected.append(dict(
@ -91,7 +92,7 @@ class ViewBuilderTestCase(test.TestCase):
expected = {'share_networks': expected}
result = self.builder.build_share_networks(
self.req, share_networks, True)
req, share_networks, True)
self.assertEqual(expected, result)
@ -104,6 +105,7 @@ class ViewBuilderTestCase(test.TestCase):
)
def test_build_share_networks_without_details_v_2_18(self,
share_networks):
req = fakes.HTTPRequest.blank('/share-networks', version="2.18")
expected = []
for share_network in share_networks:
expected.append(dict(
@ -111,6 +113,104 @@ class ViewBuilderTestCase(test.TestCase):
expected = {'share_networks': expected}
result = self.builder.build_share_networks(
self.req, share_networks, False)
req, share_networks, False)
self.assertEqual(expected, result)
@ddt.data(
{'id': 'fake_sn_id', 'name': 'fake_sn_name'},
{'id': 'fake_sn_id', 'name': 'fake_sn_name', 'fake_extra_key': 'foo'},
)
def test_build_share_network_v_2_20(self, sn):
req = fakes.HTTPRequest.blank('/share-networks', version="2.20")
expected_keys = (
'id', 'name', 'project_id', 'created_at', 'updated_at',
'neutron_net_id', 'neutron_subnet_id', 'nova_net_id',
'network_type', 'segmentation_id', 'cidr', 'ip_version',
'gateway', 'description', 'mtu')
result = self.builder.build_share_network(req, sn)
self.assertEqual(1, len(result))
self.assertIn('share_network', result)
self.assertEqual(sn['id'], result['share_network']['id'])
self.assertEqual(sn['name'], result['share_network']['name'])
self.assertEqual(len(expected_keys), len(result['share_network']))
for key in expected_keys:
self.assertIn(key, result['share_network'])
for key in result['share_network']:
self.assertIn(key, expected_keys)
@ddt.data(
[], [{
'id': 'fake_id',
'name': 'fake_name',
'project_id': 'fake_project_id',
'created_at': 'fake_created_at',
'updated_at': 'fake_updated_at',
'neutron_net_id': 'fake_neutron_net_id',
'neutron_subnet_id': 'fake_neutron_subnet_id',
'nova_net_id': 'fake_nova_net_id',
'network_type': 'fake_network_type',
'segmentation_id': 'fake_segmentation_id',
'cidr': 'fake_cidr',
'ip_version': 'fake_ip_version',
'gateway': 'fake_gateway',
'description': 'fake_description',
'mtu': 1509
},
{
'id': 'fake_id2',
'name': 'fake_name2'
}],
)
def test_build_share_networks_with_details_v_2_20(self, share_networks):
req = fakes.HTTPRequest.blank('/share-networks', version="2.20")
expected = []
for share_network in share_networks:
expected.append({
'id': share_network.get('id'),
'name': share_network.get('name'),
'project_id': share_network.get('project_id'),
'created_at': share_network.get('created_at'),
'updated_at': share_network.get('updated_at'),
'neutron_net_id': share_network.get('neutron_net_id'),
'neutron_subnet_id': share_network.get('neutron_subnet_id'),
'nova_net_id': share_network.get('nova_net_id'),
'network_type': share_network.get('network_type'),
'segmentation_id': share_network.get('segmentation_id'),
'cidr': share_network.get('cidr'),
'ip_version': share_network.get('ip_version'),
'gateway': share_network.get('gateway'),
'description': share_network.get('description'),
'mtu': share_network.get('mtu'),
})
expected = {'share_networks': expected}
result = self.builder.build_share_networks(
req, share_networks, True)
self.assertEqual(expected, result)
@ddt.data(
[],
[{'id': 'foo', 'name': 'bar'}],
[{'id': 'id1', 'name': 'name1'}, {'id': 'id2', 'name': 'name2'}],
[{'id': 'id1', 'name': 'name1'},
{'id': 'id2', 'name': 'name2', 'fake': 'I should not be returned'}],
)
def test_build_share_networks_without_details_v_2_20(self,
share_networks):
req = fakes.HTTPRequest.blank('/share-networks', version="2.20")
expected = []
for share_network in share_networks:
expected.append({
'id': share_network.get('id'),
'name': share_network.get('name')
})
expected = {'share_networks': expected}
result = self.builder.build_share_networks(
req, share_networks, False)
self.assertEqual(expected, result)

View File

@ -793,3 +793,130 @@ class RemoveHostFromDriverPrivateDataChecks(BaseMigrationChecks):
for row in rows:
self.test_case.assertTrue(hasattr(row, self.host_column_name))
self.test_case.assertEqual('unknown', row[self.host_column_name])
@map_to_migration('493eaffd79e1')
class NewMTUColumnChecks(BaseMigrationChecks):
na_table_name = 'network_allocations'
sn_table_name = 'share_networks'
na_ids = ['network_allocation_id_fake_3_%d' % i for i in (1, 2, 3)]
sn_ids = ['share_network_id_fake_3_%d' % i for i in (1, 2)]
def setup_upgrade_data(self, engine):
user_id = 'user_id'
project_id = 'project_id'
share_server_id = 'share_server_id_foo_2'
# Create share network
share_network_data = {
'id': self.sn_ids[0],
'user_id': user_id,
'project_id': project_id,
}
sn_table = utils.load_table(self.sn_table_name, engine)
engine.execute(sn_table.insert(share_network_data))
# Create share server
share_server_data = {
'id': share_server_id,
'share_network_id': share_network_data['id'],
'host': 'fake_host',
'status': 'active',
}
ss_table = utils.load_table('share_servers', engine)
engine.execute(ss_table.insert(share_server_data))
# Create network allocations
network_allocations = [
{
'id': self.na_ids[0],
'share_server_id': share_server_id,
'ip_address': '1.1.1.1',
},
{
'id': self.na_ids[1],
'share_server_id': share_server_id,
'ip_address': '2.2.2.2',
},
]
na_table = utils.load_table(self.na_table_name, engine)
engine.execute(na_table.insert(network_allocations))
def check_upgrade(self, engine, data):
na_table = utils.load_table(self.na_table_name, engine)
for na in engine.execute(na_table.select()):
self.test_case.assertTrue(hasattr(na, 'mtu'))
# Create network allocation
network_allocations = [
{
'id': self.na_ids[2],
'share_server_id': na.share_server_id,
'ip_address': '3.3.3.3',
'gateway': '3.3.3.1',
'network_type': 'vlan',
'segmentation_id': 1005,
'ip_version': 4,
'cidr': '240.0.0.0/16',
'mtu': 1509,
},
]
engine.execute(na_table.insert(network_allocations))
# Select network allocations with mtu info
for na in engine.execute(
na_table.select().where(na_table.c.mtu == '1509')):
self.test_case.assertTrue(hasattr(na, 'mtu'))
self.test_case.assertEqual(network_allocations[0]['mtu'],
getattr(na, 'mtu'))
# Select all entries and check for the value
for na in engine.execute(na_table.select()):
self.test_case.assertTrue(hasattr(na, 'mtu'))
if na['id'] == self.na_ids[2]:
self.test_case.assertEqual(network_allocations[0]['mtu'],
getattr(na, 'mtu'))
else:
self.test_case.assertIsNone(na['mtu'])
sn_table = utils.load_table(self.sn_table_name, engine)
for sn in engine.execute(sn_table.select()):
self.test_case.assertTrue(hasattr(sn, 'mtu'))
# Create share network
share_networks = [
{
'id': self.sn_ids[1],
'user_id': sn.user_id,
'project_id': sn.project_id,
'gateway': '1.1.1.1',
'name': 'name_foo_2',
'mtu': 1509,
},
]
engine.execute(sn_table.insert(share_networks))
# Select share network with MTU set
for sn in engine.execute(
sn_table.select().where(sn_table.c.name == 'name_foo_2')):
self.test_case.assertTrue(hasattr(sn, 'mtu'))
self.test_case.assertEqual(share_networks[0]['mtu'],
getattr(sn, 'mtu'))
# Select all entries and check for the value
for sn in engine.execute(sn_table.select()):
self.test_case.assertTrue(hasattr(sn, 'mtu'))
if sn['id'] == self.sn_ids[1]:
self.test_case.assertEqual(network_allocations[0]['mtu'],
getattr(sn, 'mtu'))
else:
self.test_case.assertIsNone(sn['mtu'])
def check_downgrade(self, engine):
for table_name, ids in ((self.na_table_name, self.na_ids),
(self.sn_table_name, self.sn_ids)):
table = utils.load_table(table_name, engine)
db_result = engine.execute(table.select())
self.test_case.assertTrue(db_result.rowcount >= len(ids))
for record in db_result:
self.test_case.assertFalse(hasattr(record, 'mtu'))

View File

@ -62,6 +62,7 @@ fake_share_network = {
'ip_version': 4,
'cidr': 'fake_cidr',
'gateway': 'fake_gateway',
'mtu': 1509,
}
fake_share_server = {
@ -84,6 +85,7 @@ fake_network_allocation = {
'ip_version': fake_share_network['ip_version'],
'cidr': fake_share_network['cidr'],
'gateway': fake_share_network['gateway'],
'mtu': 1509,
}
@ -252,10 +254,16 @@ class NeutronNetworkPluginTest(test.TestCase):
@mock.patch.object(db_api, 'share_network_update', mock.Mock())
def test_save_neutron_network_data(self):
neutron_nw_info = {'provider:network_type': 'vlan',
'provider:segmentation_id': 1000}
share_nw_update_dict = {'network_type': 'vlan',
'segmentation_id': 1000}
neutron_nw_info = {
'provider:network_type': 'vlan',
'provider:segmentation_id': 1000,
'mtu': 1509,
}
share_nw_update_dict = {
'network_type': 'vlan',
'segmentation_id': 1000,
'mtu': 1509,
}
with mock.patch.object(self.plugin.neutron_api,
'get_network',

View File

@ -61,7 +61,7 @@ class NovaNetworkPluginTest(test.TestCase):
gateway='20.0.0.1', gateway_v6=None,
dhcp_server='20.0.0.2', broadcast='20.0.0.255',
vpn_private_address='20.0.0.3', vpn_public_address='20.0.0.4',
dns1='20.0.0.5', dns2='20.0.0.6', vlan=None)
dns1='20.0.0.5', dns2='20.0.0.6', vlan=None, mtu=1509)
if net_type == 'vlan':
nova_net['vlan'] = 100
self.mock_object(self.instance.nova_api, 'fixed_ip_reserve')
@ -92,7 +92,7 @@ class NovaNetworkPluginTest(test.TestCase):
self.fake_context, share_network['id'],
dict(cidr=nova_net['cidr'], gateway=nova_net['gateway'],
ip_version=4, segmentation_id=nova_net['vlan'],
network_type=net_type))
network_type=net_type, mtu=1509))
self.instance.db.network_allocations_get_by_ip_address.\
assert_has_calls([
mock.call(self.fake_context, '20.0.0.7'),
@ -117,7 +117,7 @@ class NovaNetworkPluginTest(test.TestCase):
gateway='20.0.0.1', gateway_v6=None,
dhcp_server='20.0.0.254', broadcast='20.0.0.255',
vpn_private_address='20.0.0.3', vpn_public_address='20.0.0.4',
dns1='20.0.0.5', dns2='20.0.0.6', vlan=None)
dns1='20.0.0.5', dns2='20.0.0.6', vlan=None, mtu=1509)
if net_type == 'vlan':
nova_net['vlan'] = 100
self.mock_object(self.instance.nova_api, 'fixed_ip_reserve')
@ -152,7 +152,7 @@ class NovaNetworkPluginTest(test.TestCase):
self.fake_context, self.share_network['id'],
dict(cidr=nova_net['cidr'], gateway=nova_net['gateway'],
ip_version=4, segmentation_id=nova_net['vlan'],
network_type=net_type))
network_type=net_type, mtu=1509))
self.instance.db.network_allocations_get_by_ip_address.\
assert_has_calls([
mock.call(self.fake_context, '20.0.0.2'),
@ -170,7 +170,7 @@ class NovaNetworkPluginTest(test.TestCase):
gateway='20.0.0.1', gateway_v6=None,
dhcp_server='20.0.0.2', broadcast='20.0.0.255',
vpn_private_address='20.0.0.3', vpn_public_address='20.0.0.4',
dns1='20.0.0.5', dns2='20.0.0.6', vlan=100)
dns1='20.0.0.5', dns2='20.0.0.6', vlan=100, mtu=1509)
self.mock_object(
self.instance.nova_api, 'network_get',
mock.Mock(return_value=nova_net))
@ -190,7 +190,7 @@ class NovaNetworkPluginTest(test.TestCase):
self.fake_context, self.share_network['id'],
dict(cidr=nova_net['cidr'], gateway=nova_net['gateway'],
ip_version=4, segmentation_id=nova_net['vlan'],
network_type='vlan'))
network_type='vlan', mtu=1509))
self.assertEqual(
248,
self.instance.db.network_allocations_get_by_ip_address.call_count)

View File

@ -311,7 +311,8 @@ class StandaloneNetworkPluginTest(test.TestCase):
dict(network_type=None, segmentation_id=None,
cidr=six.text_type(instance.net.cidr),
gateway=six.text_type(instance.gateway),
ip_version=4))
ip_version=4,
mtu=1500))
def test_allocate_network_zero_addresses_ipv6(self):
data = {
@ -334,7 +335,8 @@ class StandaloneNetworkPluginTest(test.TestCase):
dict(network_type=None, segmentation_id=None,
cidr=six.text_type(instance.net.cidr),
gateway=six.text_type(instance.gateway),
ip_version=6))
ip_version=6,
mtu=1500))
def test_allocate_network_one_ip_address_ipv4_no_usages_exist(self):
data = {
@ -363,6 +365,7 @@ class StandaloneNetworkPluginTest(test.TestCase):
'cidr': '10.0.0.0/24',
'gateway': '10.0.0.1',
'ip_version': 4,
'mtu': 1500,
}
instance.db.share_network_update.assert_called_once_with(
fake_context, fake_share_network['id'], na_data)
@ -408,6 +411,7 @@ class StandaloneNetworkPluginTest(test.TestCase):
'cidr': six.text_type(instance.net.cidr),
'gateway': six.text_type(instance.gateway),
'ip_version': 4,
'mtu': 1500,
}
instance.db.share_network_update.assert_called_once_with(
ctxt, fake_share_network['id'], dict(**na_data))
@ -452,6 +456,7 @@ class StandaloneNetworkPluginTest(test.TestCase):
dict(network_type=None, segmentation_id=None,
cidr=six.text_type(instance.net.cidr),
gateway=six.text_type(instance.gateway),
ip_version=4))
ip_version=4,
mtu=1500))
instance.db.network_allocations_get_by_ip_address.assert_has_calls(
[mock.call(fake_context, '10.0.0.2')])

View File

@ -34,7 +34,7 @@ ShareGroup = [
help="The minimum api microversion is configured to be the "
"value of the minimum microversion supported by Manila."),
cfg.StrOpt("max_api_microversion",
default="2.19",
default="2.20",
help="The maximum api microversion is configured to be the "
"value of the latest microversion supported by Manila."),
cfg.StrOpt("region",

View File

@ -53,6 +53,10 @@ class ShareNetworkListMixin(object):
if utils.is_microversion_supported('2.18'):
keys.append('gateway')
# In v2.20 and beyond, we expect mtu.
if utils.is_microversion_supported('2.20'):
keys.append('mtu')
[self.assertIn(key, sn.keys()) for sn in listed for key in keys]
@test.attr(type=[base.TAG_POSITIVE, base.TAG_API])

View File

@ -0,0 +1,4 @@
---
features:
- Store network MTU value into DB to make it possible for drivers with share
server support to support different values than 1500.