Merge "Do not have heat to connect to external service"
diff --git a/tempest/api/compute/servers/test_instance_actions.py b/tempest/api/compute/servers/test_instance_actions.py
index a229df8..e50881f 100644
--- a/tempest/api/compute/servers/test_instance_actions.py
+++ b/tempest/api/compute/servers/test_instance_actions.py
@@ -28,17 +28,17 @@
     @classmethod
     def resource_setup(cls):
         super(InstanceActionsTestJSON, cls).resource_setup()
-        server = cls.create_test_server(wait_until='ACTIVE')
-        cls.request_id = server.response['x-compute-request-id']
-        cls.server_id = server['id']
+        cls.server = cls.create_test_server(wait_until='ACTIVE')
+        cls.request_id = cls.server.response['x-compute-request-id']
 
     @test.idempotent_id('77ca5cc5-9990-45e0-ab98-1de8fead201a')
     def test_list_instance_actions(self):
         # List actions of the provided server
-        self.client.reboot_server(self.server_id, type='HARD')
-        waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+        self.client.reboot_server(self.server['id'], type='HARD')
+        waiters.wait_for_server_status(self.client,
+                                       self.server['id'], 'ACTIVE')
 
-        body = (self.client.list_instance_actions(self.server_id)
+        body = (self.client.list_instance_actions(self.server['id'])
                 ['instanceActions'])
         self.assertEqual(len(body), 2, str(body))
         self.assertEqual(sorted([i['action'] for i in body]),
@@ -48,8 +48,8 @@
     def test_get_instance_action(self):
         # Get the action details of the provided server
         body = self.client.show_instance_action(
-            self.server_id, self.request_id)['instanceAction']
-        self.assertEqual(self.server_id, body['instance_uuid'])
+            self.server['id'], self.request_id)['instanceAction']
+        self.assertEqual(self.server['id'], body['instance_uuid'])
         self.assertEqual('create', body['action'])
 
 
diff --git a/tempest/api/compute/servers/test_instance_actions_negative.py b/tempest/api/compute/servers/test_instance_actions_negative.py
index 54ec6aa..33fed08 100644
--- a/tempest/api/compute/servers/test_instance_actions_negative.py
+++ b/tempest/api/compute/servers/test_instance_actions_negative.py
@@ -29,8 +29,7 @@
     @classmethod
     def resource_setup(cls):
         super(InstanceActionsNegativeTestJSON, cls).resource_setup()
-        server = cls.create_test_server(wait_until='ACTIVE')
-        cls.server_id = server['id']
+        cls.server = cls.create_test_server(wait_until='ACTIVE')
 
     @test.attr(type=['negative'])
     @test.idempotent_id('67e1fce6-7ec2-45c6-92d4-0a8f1a632910')
@@ -46,4 +45,4 @@
     def test_get_instance_action_invalid_request(self):
         # Get the action details of the provided server with invalid request
         self.assertRaises(lib_exc.NotFound, self.client.show_instance_action,
-                          self.server_id, '999')
+                          self.server['id'], '999')
diff --git a/tempest/api/compute/servers/test_server_metadata.py b/tempest/api/compute/servers/test_server_metadata.py
index 9c07677..cb66e81 100644
--- a/tempest/api/compute/servers/test_server_metadata.py
+++ b/tempest/api/compute/servers/test_server_metadata.py
@@ -28,18 +28,17 @@
     @classmethod
     def resource_setup(cls):
         super(ServerMetadataTestJSON, cls).resource_setup()
-        server = cls.create_test_server(metadata={}, wait_until='ACTIVE')
-        cls.server_id = server['id']
+        cls.server = cls.create_test_server(metadata={}, wait_until='ACTIVE')
 
     def setUp(self):
         super(ServerMetadataTestJSON, self).setUp()
         meta = {'key1': 'value1', 'key2': 'value2'}
-        self.client.set_server_metadata(self.server_id, meta)['metadata']
+        self.client.set_server_metadata(self.server['id'], meta)['metadata']
 
     @test.idempotent_id('479da087-92b3-4dcf-aeb3-fd293b2d14ce')
     def test_list_server_metadata(self):
         # All metadata key/value pairs for a server should be returned
-        resp_metadata = (self.client.list_server_metadata(self.server_id)
+        resp_metadata = (self.client.list_server_metadata(self.server['id'])
                          ['metadata'])
 
         # Verify the expected metadata items are in the list
@@ -51,12 +50,12 @@
         # The server's metadata should be replaced with the provided values
         # Create a new set of metadata for the server
         req_metadata = {'meta2': 'data2', 'meta3': 'data3'}
-        self.client.set_server_metadata(self.server_id,
+        self.client.set_server_metadata(self.server['id'],
                                         req_metadata)['metadata']
 
         # Verify the expected values are correct, and that the
         # previous values have been removed
-        resp_metadata = (self.client.list_server_metadata(self.server_id)
+        resp_metadata = (self.client.list_server_metadata(self.server['id'])
                          ['metadata'])
         self.assertEqual(resp_metadata, req_metadata)
 
@@ -65,10 +64,10 @@
         # The server's metadata values should be updated to the
         # provided values
         meta = {'key1': 'alt1', 'key3': 'value3'}
-        self.client.update_server_metadata(self.server_id, meta)
+        self.client.update_server_metadata(self.server['id'], meta)
 
         # Verify the values have been updated to the proper values
-        resp_metadata = (self.client.list_server_metadata(self.server_id)
+        resp_metadata = (self.client.list_server_metadata(self.server['id'])
                          ['metadata'])
         expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'}
         self.assertEqual(expected, resp_metadata)
@@ -78,8 +77,8 @@
         # The original metadata should not be lost if empty metadata body is
         # passed
         meta = {}
-        self.client.update_server_metadata(self.server_id, meta)
-        resp_metadata = (self.client.list_server_metadata(self.server_id)
+        self.client.update_server_metadata(self.server['id'], meta)
+        resp_metadata = (self.client.list_server_metadata(self.server['id'])
                          ['metadata'])
         expected = {'key1': 'value1', 'key2': 'value2'}
         self.assertEqual(expected, resp_metadata)
@@ -87,7 +86,7 @@
     @test.idempotent_id('3043c57d-7e0e-49a6-9a96-ad569c265e6a')
     def test_get_server_metadata_item(self):
         # The value for a specific metadata key should be returned
-        meta = self.client.show_server_metadata_item(self.server_id,
+        meta = self.client.show_server_metadata_item(self.server['id'],
                                                      'key2')['meta']
         self.assertEqual('value2', meta['key2'])
 
@@ -96,10 +95,10 @@
         # The item's value should be updated to the provided value
         # Update the metadata value
         meta = {'nova': 'alt'}
-        self.client.set_server_metadata_item(self.server_id, 'nova', meta)
+        self.client.set_server_metadata_item(self.server['id'], 'nova', meta)
 
         # Verify the meta item's value has been updated
-        resp_metadata = (self.client.list_server_metadata(self.server_id)
+        resp_metadata = (self.client.list_server_metadata(self.server['id'])
                          ['metadata'])
         expected = {'key1': 'value1', 'key2': 'value2', 'nova': 'alt'}
         self.assertEqual(expected, resp_metadata)
@@ -107,10 +106,10 @@
     @test.idempotent_id('127642d6-4c7b-4486-b7cd-07265a378658')
     def test_delete_server_metadata_item(self):
         # The metadata value/key pair should be deleted from the server
-        self.client.delete_server_metadata_item(self.server_id, 'key1')
+        self.client.delete_server_metadata_item(self.server['id'], 'key1')
 
         # Verify the metadata item has been removed
-        resp_metadata = (self.client.list_server_metadata(self.server_id)
+        resp_metadata = (self.client.list_server_metadata(self.server['id'])
                          ['metadata'])
         expected = {'key2': 'value2'}
         self.assertEqual(expected, resp_metadata)
diff --git a/tempest/api/compute/servers/test_server_metadata_negative.py b/tempest/api/compute/servers/test_server_metadata_negative.py
index cbe70e2..aae9101 100644
--- a/tempest/api/compute/servers/test_server_metadata_negative.py
+++ b/tempest/api/compute/servers/test_server_metadata_negative.py
@@ -31,9 +31,7 @@
     def resource_setup(cls):
         super(ServerMetadataNegativeTestJSON, cls).resource_setup()
         cls.tenant_id = cls.client.tenant_id
-        server = cls.create_test_server(metadata={}, wait_until='ACTIVE')
-
-        cls.server_id = server['id']
+        cls.server = cls.create_test_server(metadata={}, wait_until='ACTIVE')
 
     @test.attr(type=['negative'])
     @test.idempotent_id('fe114a8f-3a57-4eff-9ee2-4e14628df049')
@@ -87,7 +85,7 @@
         meta = {'testkey': 'testvalue'}
         self.assertRaises(lib_exc.BadRequest,
                           self.client.set_server_metadata_item,
-                          self.server_id, 'key', meta)
+                          self.server['id'], 'key', meta)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('0df38c2a-3d4e-4db5-98d8-d4d9fa843a12')
@@ -118,7 +116,7 @@
         meta = {'': 'data1'}
         self.assertRaises(lib_exc.BadRequest,
                           self.client.update_server_metadata,
-                          self.server_id, meta=meta)
+                          self.server['id'], meta=meta)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('6bbd88e1-f8b3-424d-ba10-ae21c45ada8d')
@@ -146,14 +144,14 @@
             req_metadata['key' + str(num)] = 'val' + str(num)
         self.assertRaises((lib_exc.OverLimit, lib_exc.Forbidden),
                           self.client.set_server_metadata,
-                          self.server_id, req_metadata)
+                          self.server['id'], req_metadata)
 
         # A 403 Forbidden or 413 Overlimit (old behaviour) exception
         # will be raised while exceeding metadata items limit for
         # tenant.
         self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
                           self.client.update_server_metadata,
-                          self.server_id, req_metadata)
+                          self.server['id'], req_metadata)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('96100343-7fa9-40d8-80fa-d29ef588ce1c')
@@ -163,7 +161,7 @@
         meta = {'': 'data1'}
         self.assertRaises(lib_exc.BadRequest,
                           self.client.set_server_metadata,
-                          self.server_id, meta=meta)
+                          self.server['id'], meta=meta)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('64a91aee-9723-4863-be44-4c9d9f1e7d0e')
@@ -173,4 +171,4 @@
         meta = {'meta1': 'data1'}
         self.assertRaises(lib_exc.BadRequest,
                           self.client.set_server_metadata,
-                          self.server_id, meta=meta, no_metadata_field=True)
+                          self.server['id'], meta=meta, no_metadata_field=True)
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index e620e03..08c34d3 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -40,8 +40,7 @@
     @classmethod
     def resource_setup(cls):
         super(VirtualInterfacesTestJSON, cls).resource_setup()
-        server = cls.create_test_server(wait_until='ACTIVE')
-        cls.server_id = server['id']
+        cls.server = cls.create_test_server(wait_until='ACTIVE')
 
     @test.idempotent_id('96c4e2ef-5e4d-4d7f-87f5-fed6dca18016')
     @test.services('network')
@@ -53,9 +52,9 @@
             # TODO(mriedem): After a microversion implements the API for
             # neutron, a 400 should be a failure for nova-network and neutron.
             with testtools.ExpectedException(exceptions.BadRequest):
-                self.client.list_virtual_interfaces(self.server_id)
+                self.client.list_virtual_interfaces(self.server['id'])
         else:
-            output = self.client.list_virtual_interfaces(self.server_id)
+            output = self.client.list_virtual_interfaces(self.server['id'])
             self.assertIsNotNone(output)
             virt_ifaces = output
             self.assertNotEqual(0, len(virt_ifaces['virtual_interfaces']),
diff --git a/tempest/api/compute/test_live_block_migration_negative.py b/tempest/api/compute/test_live_block_migration_negative.py
index ffd274f..bd2b185 100644
--- a/tempest/api/compute/test_live_block_migration_negative.py
+++ b/tempest/api/compute/test_live_block_migration_negative.py
@@ -49,9 +49,8 @@
         # Migrating to an invalid host should not change the status
         target_host = data_utils.rand_name('host')
         server = self.create_test_server(wait_until="ACTIVE")
-        server_id = server['id']
 
         self.assertRaises(lib_exc.BadRequest, self._migrate_server_to,
-                          server_id, target_host)
-        waiters.wait_for_server_status(self.servers_client, server_id,
+                          server['id'], target_host)
+        waiters.wait_for_server_status(self.servers_client, server['id'],
                                        'ACTIVE')
diff --git a/tempest/api/network/admin/test_routers_dvr.py b/tempest/api/network/admin/test_routers_dvr.py
index 36cb15f..66feba8 100644
--- a/tempest/api/network/admin/test_routers_dvr.py
+++ b/tempest/api/network/admin/test_routers_dvr.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.api.network import base_routers as base
 from tempest.common.utils import data_utils
 from tempest import test
@@ -80,6 +82,8 @@
         self.assertFalse(router['router']['distributed'])
 
     @test.idempotent_id('acd43596-c1fb-439d-ada8-31ad48ae3c2e')
+    @testtools.skipUnless(test.is_extension_enabled('l3-ha', 'network'),
+                          'HA routers are not available.')
     def test_centralized_router_update_to_dvr(self):
         """Test centralized router update
 
@@ -95,9 +99,11 @@
         """
         name = data_utils.rand_name('router')
         # router needs to be in admin state down in order to be upgraded to DVR
+        # l3ha routers are not upgradable to dvr, make it explicitly non ha
         router = self.admin_routers_client.create_router(name=name,
                                                          distributed=False,
-                                                         admin_state_up=False)
+                                                         admin_state_up=False,
+                                                         ha=False)
         self.addCleanup(self.admin_routers_client.delete_router,
                         router['router']['id'])
         self.assertFalse(router['router']['distributed'])
diff --git a/tempest/clients.py b/tempest/clients.py
index be6bc02..f358d91 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -19,7 +19,6 @@
 
 from tempest.common import negative_rest_client
 from tempest import config
-from tempest import exceptions
 from tempest.lib import auth
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.services import clients
@@ -350,7 +349,7 @@
     # kwargs for auth provider match the common ones used by service clients
     default_params = config.service_client_config()
     if credentials is None:
-        raise exceptions.InvalidCredentials(
+        raise lib_exc.InvalidCredentials(
             'Credentials must be specified')
     auth_provider_class, auth_url = get_auth_provider_class(
         credentials)
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 cd3501e..831be99 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 a295b6a..b527c3d 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()))