neutron: added test case to check connectivity using MTU sized frames
Properly configured setup must allow frames of network MTU size (as
reported by Neutron net-mtu API extension) through the network.
Sadly, Cirros images ship ping tools that don't support the -M argument
that allows to set Don't Fragment flag, so the only way to enforce it is
from outside the instance.
Change-Id: I1dd0633e48dbc9b67cd79e610e959b514304a4b5
Related-Bug: #1623876
diff --git a/tempest/common/utils/net_utils.py b/tempest/common/utils/net_utils.py
index f0d3da3..867b3dd 100644
--- a/tempest/common/utils/net_utils.py
+++ b/tempest/common/utils/net_utils.py
@@ -51,3 +51,23 @@
return addrs
msg = "Insufficient IP addresses available"
raise lib_exc.BadRequest(message=msg)
+
+
+def get_ping_payload_size(mtu, ip_version):
+ """Return the maximum size of ping payload that will fit into MTU."""
+ if not mtu:
+ return None
+ if ip_version == 4:
+ ip_header = 20
+ icmp_header = 8
+ else:
+ ip_header = 40
+ icmp_header = 4
+ res = mtu - ip_header - icmp_header
+ if res < 0:
+ raise lib_exc.BadRequest(
+ message='MTU = %(mtu)d is too low for IPv%(ip_version)d' % {
+ 'mtu': mtu,
+ 'ip_version': ip_version,
+ })
+ return res
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index a88194c..9fdeba2 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -26,6 +26,7 @@
from tempest.common import image as common_image
from tempest.common.utils import data_utils
from tempest.common.utils.linux import remote_client
+from tempest.common.utils import net_utils
from tempest.common import waiters
from tempest import config
from tempest import exceptions
@@ -495,9 +496,18 @@
server_id, 'ACTIVE')
def ping_ip_address(self, ip_address, should_succeed=True,
- ping_timeout=None):
+ ping_timeout=None, mtu=None):
timeout = ping_timeout or CONF.validation.ping_timeout
- cmd = ['ping', '-c1', '-w1', ip_address]
+ cmd = ['ping', '-c1', '-w1']
+
+ if mtu:
+ cmd += [
+ # don't fragment
+ '-M', 'do',
+ # ping receives just the size of ICMP payload
+ '-s', str(net_utils.get_ping_payload_size(mtu, 4))
+ ]
+ cmd.append(ip_address)
def ping():
proc = subprocess.Popen(cmd,
@@ -525,7 +535,8 @@
def check_vm_connectivity(self, ip_address,
username=None,
private_key=None,
- should_connect=True):
+ should_connect=True,
+ mtu=None):
"""Check server connectivity
:param ip_address: server to test against
@@ -534,6 +545,7 @@
:param should_connect: True/False indicates positive/negative test
positive - attempt ping and ssh
negative - attempt ping and fail if succeed
+ :param mtu: network MTU to use for connectivity validation
:raises: AssertError if the result of the connectivity check does
not match the value of the should_connect param
@@ -543,7 +555,8 @@
else:
msg = "ip address %s is reachable" % ip_address
self.assertTrue(self.ping_ip_address(ip_address,
- should_succeed=should_connect),
+ should_succeed=should_connect,
+ mtu=mtu),
msg=msg)
if should_connect:
# no need to check ssh for negative connectivity
@@ -551,7 +564,7 @@
def check_public_network_connectivity(self, ip_address, username,
private_key, should_connect=True,
- msg=None, servers=None):
+ msg=None, servers=None, mtu=None):
# The target login is assumed to have been configured for
# key-based authentication by cloud-init.
LOG.debug('checking network connections to IP %s with user: %s' %
@@ -560,7 +573,8 @@
self.check_vm_connectivity(ip_address,
username,
private_key,
- should_connect=should_connect)
+ should_connect=should_connect,
+ mtu=mtu)
except Exception:
ex_msg = 'Public network connectivity check failed'
if msg:
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 519dbec..71cabea 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -183,7 +183,7 @@
def check_public_network_connectivity(
self, should_connect=True, msg=None,
- should_check_floating_ip_status=True):
+ should_check_floating_ip_status=True, mtu=None):
"""Verifies connectivty to a VM via public network and floating IP
and verifies floating IP has resource status is correct.
@@ -195,6 +195,7 @@
to indicate the context of the failure
:param should_check_floating_ip_status: bool. should status of
floating_ip be checked or not
+ :param mtu: int. MTU network to use for connectivity validation
"""
ssh_login = CONF.validation.image_ssh_user
floating_ip, server = self.floating_ip_tuple
@@ -210,7 +211,7 @@
# call the common method in the parent class
super(TestNetworkBasicOps, self).check_public_network_connectivity(
ip_address, ssh_login, private_key, should_connect, msg,
- self.servers)
+ self.servers, mtu=mtu)
def _disassociate_floating_ips(self):
floating_ip, server = self.floating_ip_tuple
@@ -410,6 +411,16 @@
msg="after re-associate "
"floating ip")
+ @test.idempotent_id('b158ea55-472e-4086-8fa9-c64ac0c6c1d0')
+ @testtools.skipUnless(test.is_extension_enabled('net-mtu', 'network'),
+ 'No way to calculate MTU for networks')
+ @test.services('compute', 'network')
+ def test_mtu_sized_frames(self):
+ """Validate that network MTU sized frames fit through."""
+ self._setup_network_and_servers()
+ self.check_public_network_connectivity(
+ should_connect=True, mtu=self.network['mtu'])
+
@test.idempotent_id('1546850e-fbaa-42f5-8b5f-03d8a6a95f15')
@testtools.skipIf(CONF.baremetal.driver_enabled,
'Baremetal relies on a shared physical network.')
diff --git a/tempest/tests/common/utils/test_net_utils.py b/tempest/tests/common/utils/test_net_utils.py
new file mode 100644
index 0000000..83c6bcc
--- /dev/null
+++ b/tempest/tests/common/utils/test_net_utils.py
@@ -0,0 +1,33 @@
+# 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.
+
+import mock
+
+from tempest.common.utils import net_utils
+from tempest.lib import exceptions as lib_exc
+from tempest.tests import base
+
+
+class TestGetPingPayloadSize(base.TestCase):
+
+ def test_ipv4(self):
+ self.assertEqual(1422, net_utils.get_ping_payload_size(1450, 4))
+
+ def test_ipv6(self):
+ self.assertEqual(1406, net_utils.get_ping_payload_size(1450, 6))
+
+ def test_too_low_mtu(self):
+ self.assertRaises(
+ lib_exc.BadRequest, net_utils.get_ping_payload_size, 10, 4)
+
+ def test_None(self):
+ self.assertIsNone(net_utils.get_ping_payload_size(None, mock.Mock()))