Merge "Recover ssh interface config and interface reload"
diff --git a/neutron_tempest_plugin/api/clients.py b/neutron_tempest_plugin/api/clients.py
index 2855a7a..053e5ea 100644
--- a/neutron_tempest_plugin/api/clients.py
+++ b/neutron_tempest_plugin/api/clients.py
@@ -89,7 +89,8 @@
         self.interfaces_client = interfaces_client.InterfacesClient(
             self.auth_provider, **params)
         self.keypairs_client = keypairs_client.KeyPairsClient(
-            self.auth_provider, **params)
+            self.auth_provider, ssh_key_type=CONF.validation.ssh_key_type,
+            **params)
         self.hv_client = hypervisor_client.HypervisorClient(
             self.auth_provider, **params)
         self.az_client = availability_zone_client.AvailabilityZoneClient(
diff --git a/neutron_tempest_plugin/api/test_qos.py b/neutron_tempest_plugin/api/test_qos.py
index 59a0eb6..2929542 100644
--- a/neutron_tempest_plugin/api/test_qos.py
+++ b/neutron_tempest_plugin/api/test_qos.py
@@ -1399,8 +1399,6 @@
 
 
 class QosMinimumPpsRuleTestJSON(base.BaseAdminNetworkTest):
-    RULE_NAME = qos_consts.RULE_TYPE_MINIMUM_PACKET_RATE + "_rule"
-    RULES_NAME = RULE_NAME + "s"
     required_extensions = [qos_apidef.ALIAS]
 
     @classmethod
@@ -1419,6 +1417,8 @@
     def setUp(self):
         super(QosMinimumPpsRuleTestJSON, self).setUp()
         self.policy_name = data_utils.rand_name(name='test', prefix='policy')
+        self.RULE_NAME = qos_consts.RULE_TYPE_MINIMUM_PACKET_RATE + "_rule"
+        self.RULES_NAME = self.RULE_NAME + "s"
 
     def _create_qos_min_pps_rule(self, policy_id, rule_data):
         rule = self.min_pps_client.create_minimum_packet_rate_rule(
diff --git a/neutron_tempest_plugin/common/ssh.py b/neutron_tempest_plugin/common/ssh.py
index 8334521..4cb1474 100644
--- a/neutron_tempest_plugin/common/ssh.py
+++ b/neutron_tempest_plugin/common/ssh.py
@@ -62,7 +62,8 @@
             host=host, username=username, password=password, timeout=timeout,
             pkey=pkey, channel_timeout=channel_timeout,
             look_for_keys=look_for_keys, key_filename=key_filename, port=port,
-            proxy_client=proxy_client)
+            proxy_client=proxy_client,
+            ssh_key_type=CONF.validation.ssh_key_type)
 
     @classmethod
     def create_proxy_client(cls, look_for_keys=True, **kwargs):
diff --git a/neutron_tempest_plugin/common/utils.py b/neutron_tempest_plugin/common/utils.py
index 898e4b9..4ccec72 100644
--- a/neutron_tempest_plugin/common/utils.py
+++ b/neutron_tempest_plugin/common/utils.py
@@ -29,11 +29,14 @@
 
 from tempest.lib import exceptions
 
+from neutron_tempest_plugin import config
+
 
 SCHEMA_PORT_MAPPING = {
     "http": 80,
     "https": 443,
 }
+CONF = config.CONF
 
 
 class classproperty(object):
@@ -161,13 +164,20 @@
         return 'attempt_{}'.format(str(self.test_attempt).zfill(3))
 
     def _start_connection(self):
+        if CONF.neutron_plugin_options.default_image_is_advanced:
+            server_exec_method = self.server_ssh.execute_script
+            client_exec_method = self.client_ssh.execute_script
+        else:
+            server_exec_method = self.server_ssh.exec_command
+            client_exec_method = self.client_ssh.exec_command
+
         self.server_ssh.exec_command(
                 'echo "{}" > input.txt'.format(self.test_str))
-        self.server_ssh.exec_command('tail -f input.txt | nc -lp '
+        server_exec_method('tail -f input.txt | nc -lp '
                 '{} &> output.txt &'.format(self.port))
         self.client_ssh.exec_command(
                 'echo "{}" > input.txt'.format(self.test_str))
-        self.client_ssh.exec_command('tail -f input.txt | nc {} {} &>'
+        client_exec_method('tail -f input.txt | nc {} {} &>'
                 'output.txt &'.format(self.ip, self.port))
 
     def _test_connection(self):
@@ -203,9 +213,11 @@
                 self._test_connection, timeout=timeout, sleep=sleep_timer)
 
     def __exit__(self, type, value, traceback):
-        self.server_ssh.exec_command('sudo killall nc || killall nc')
+        self.server_ssh.exec_command('sudo killall nc || killall nc || '
+                                     'echo "True"')
         self.server_ssh.exec_command(
                 'sudo killall tail || killall tail || echo "True"')
-        self.client_ssh.exec_command('sudo killall nc || killall nc')
+        self.client_ssh.exec_command('sudo killall nc || killall nc || '
+                                     'echo "True"')
         self.client_ssh.exec_command(
                 'sudo killall tail || killall tail || echo "True"')
diff --git a/neutron_tempest_plugin/fwaas/scenario/fwaas_v2_base.py b/neutron_tempest_plugin/fwaas/scenario/fwaas_v2_base.py
index 00cdf2c..f8eb44c 100644
--- a/neutron_tempest_plugin/fwaas/scenario/fwaas_v2_base.py
+++ b/neutron_tempest_plugin/fwaas/scenario/fwaas_v2_base.py
@@ -46,6 +46,7 @@
             try:
                 client = ssh.Client(ip_address, username, pkey=private_key,
                                     channel_timeout=connect_timeout,
+                                    ssh_key_type=CONF.validation.ssh_key_type,
                                     **kwargs)
                 client.test_connection_auth()
                 self.assertTrue(should_connect, "Unexpectedly reachable")
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py
index 85cc810..3ec231e 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py
@@ -214,7 +214,8 @@
         left_server = self._create_server()
         ssh_client = ssh.Client(left_server['fip']['floating_ip_address'],
                                 CONF.validation.image_ssh_user,
-                                pkey=self.keypair['private_key'])
+                                pkey=self.keypair['private_key'],
+                                ssh_key_type=CONF.validation.ssh_key_type)
 
         # check LEFT -> RIGHT connectivity via BGP advertised routes
         self.check_remote_connectivity(
diff --git a/neutron_tempest_plugin/scenario/test_dhcp.py b/neutron_tempest_plugin/scenario/test_dhcp.py
index b95eaa2..d0545e2 100644
--- a/neutron_tempest_plugin/scenario/test_dhcp.py
+++ b/neutron_tempest_plugin/scenario/test_dhcp.py
@@ -11,14 +11,18 @@
 #    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 netaddr
+from neutron_lib import constants
 from oslo_log import log
 from paramiko import ssh_exception as ssh_exc
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
+import testtools
 
 from neutron_tempest_plugin.common import ssh
+from neutron_tempest_plugin.common import utils as neutron_utils
 from neutron_tempest_plugin import config
 from neutron_tempest_plugin.scenario import base
 
@@ -92,3 +96,96 @@
             self._log_console_output([server])
             self._log_local_network_status()
             raise
+
+
+class DHCPPortUpdateTest(base.BaseTempestTestCase):
+
+    credentials = ['primary', 'admin']
+
+    @classmethod
+    def resource_setup(cls):
+        super(DHCPPortUpdateTest, cls).resource_setup()
+        cls.rand_name = data_utils.rand_name(
+            cls.__name__.rsplit('.', 1)[-1])
+        cls.network = cls.create_network(name=cls.rand_name)
+        cls.router = cls.create_router_by_client()
+        cls.keypair = cls.create_keypair(name=cls.rand_name)
+        cls.security_group = cls.create_security_group(name=cls.rand_name)
+        cls.create_loginable_secgroup_rule(cls.security_group['id'])
+        cls.create_pingable_secgroup_rule(cls.security_group['id'])
+
+    @testtools.skipUnless(
+        CONF.neutron_plugin_options.firewall_driver == 'ovn',
+        "OVN driver is required to run this test - "
+        "LP#1942794 solution only applied to OVN")
+    @decorators.idempotent_id('8171cc68-9dbb-46ca-b065-17b5b2e26094')
+    def test_modify_dhcp_port_ip_address(self):
+        """Test Scenario
+
+        1) Create a network and a subnet with DHCP enabled
+        2) Modify the default IP address from the subnet DHCP port
+        3) Create a server in this network and check ssh connectivity
+
+        For the step 3), the server needs to obtain ssh keys from the metadata
+
+        Related bug: LP#1942794
+        """
+        # create subnet (dhcp is enabled by default)
+        subnet = self.create_subnet(network=self.network, name=self.rand_name)
+
+        def _get_dhcp_ports():
+            # in some cases, like ML2/OVS, the subnet port associated to DHCP
+            # is created with device_owner='network:dhcp'
+            dhcp_ports = self.client.list_ports(
+                network_id=self.network['id'],
+                device_owner=constants.DEVICE_OWNER_DHCP)['ports']
+            # in other cases, like ML2/OVN, the subnet port used for metadata
+            # is created with device_owner='network:distributed'
+            distributed_ports = self.client.list_ports(
+                network_id=self.network['id'],
+                device_owner=constants.DEVICE_OWNER_DISTRIBUTED)['ports']
+            self.dhcp_ports = dhcp_ports + distributed_ports
+            self.assertLessEqual(
+                len(self.dhcp_ports), 1, msg='Only one port was expected')
+            return len(self.dhcp_ports) == 1
+
+        # obtain the dhcp port
+        # in some cases this port is not created together with the subnet, but
+        # immediately after it, so some delay may be needed and that is the
+        # reason why a waiter function is used here
+        self.dhcp_ports = []
+        neutron_utils.wait_until_true(
+            lambda: _get_dhcp_ports(),
+            timeout=10)
+        dhcp_port = self.dhcp_ports[0]
+
+        # modify DHCP port IP address
+        old_dhcp_port_ip = netaddr.IPAddress(
+            dhcp_port['fixed_ips'][0]['ip_address'])
+        if str(old_dhcp_port_ip) != subnet['allocation_pools'][0]['end']:
+            new_dhcp_port_ip = str(old_dhcp_port_ip + 1)
+        else:
+            new_dhcp_port_ip = str(old_dhcp_port_ip - 1)
+        self.update_port(port=dhcp_port,
+                         fixed_ips=[{'subnet_id': subnet['id'],
+                                     'ip_address': new_dhcp_port_ip}])
+
+        # create server
+        server = self.create_server(
+            flavor_ref=CONF.compute.flavor_ref,
+            image_ref=CONF.compute.image_ref,
+            key_name=self.keypair['name'],
+            security_groups=[{'name': self.security_group['name']}],
+            networks=[{'uuid': self.network['id']}])
+
+        # attach fip to the server
+        self.create_router_interface(self.router['id'], subnet['id'])
+        server_port = self.client.list_ports(
+            network_id=self.network['id'],
+            device_id=server['server']['id'])['ports'][0]
+        fip = self.create_floatingip(port_id=server_port['id'])
+
+        # check connectivity
+        self.check_connectivity(fip['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
diff --git a/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py b/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py
index 1a51198..92eed9e 100644
--- a/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py
+++ b/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py
@@ -233,7 +233,8 @@
         left_server = self._create_server()
         ssh_client = ssh.Client(left_server['fip']['floating_ip_address'],
                                 CONF.validation.image_ssh_user,
-                                pkey=self.keypair['private_key'])
+                                pkey=self.keypair['private_key'],
+                                ssh_key_type=CONF.validation.ssh_key_type)
 
         # check LEFT -> RIGHT connectivity via VPN
         self.check_remote_connectivity(ssh_client, right_ip,
diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml
index e27b01d..9e36810 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -459,13 +459,9 @@
       network_api_extensions: *api_extensions
       network_api_extensions_ovn:
         - vlan-transparent
-      # TODO(slaweq): Remove test_trunk_subport_lifecycle test from the
-      # blacklist when bug https://bugs.launchpad.net/neutron/+bug/1885900 will
-      # be fixed
       # TODO(jlibosva): Remove the NetworkWritableMtuTest test from the list
       # once east/west fragmentation is supported in core OVN
       tempest_exclude_regex: "\
-          (^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_trunk_subport_lifecycle)|\
           (^neutron_tempest_plugin.scenario.test_mtu.NetworkWritableMtuTest)"
       devstack_localrc:
         Q_AGENT: ovn
@@ -1011,6 +1007,43 @@
       - ^zuul.d/(?!(project)).*\.yaml
 
 - job:
+    name: neutron-tempest-plugin-fwaas
+    parent: neutron-tempest-plugin-base
+    timeout: 10800
+    required-projects:
+      - openstack/devstack-gate
+      - openstack/neutron-fwaas
+      - openstack/neutron
+      - openstack/neutron-tempest-plugin
+      - openstack/tempest
+    vars:
+      tempest_test_regex: ^neutron_tempest_plugin\.fwaas
+      devstack_plugins:
+        neutron-fwaas: https://opendev.org/openstack/neutron-fwaas.git
+        neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin.git
+      network_api_extensions_common: *api_extensions
+      network_api_extensions_fwaas:
+        - fwaas_v2
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_fwaas) | join(',') }}"
+        Q_AGENT: openvswitch
+        Q_ML2_TENANT_NETWORK_TYPE: vxlan
+        Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
+      devstack_services:
+        # Disable OVN services
+        br-ex-tcpdump: false
+        br-int-flows: false
+        ovn-controller: false
+        ovn-northd: false
+        q-ovn-metadata-agent: false
+        # Neutron services
+        q-agt: true
+        q-dhcp: true
+        q-meta: true
+        q-metering: true
+        q-l3: true
+
+- job:
     name: neutron-tempest-plugin-vpnaas
     parent: neutron-tempest-plugin-base
     timeout: 3900
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index caf83da..3463d84 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -202,6 +202,7 @@
         - neutron-tempest-plugin-dynamic-routing-victoria
         - neutron-tempest-plugin-dynamic-routing-wallaby
         - neutron-tempest-plugin-dynamic-routing-xena
+        - neutron-tempest-plugin-fwaas
         - neutron-tempest-plugin-vpnaas
         - neutron-tempest-plugin-vpnaas-victoria
         - neutron-tempest-plugin-vpnaas-wallaby
@@ -214,3 +215,4 @@
         - neutron-tempest-plugin-sfc
         - neutron-tempest-plugin-bgpvpn-bagpipe
         - neutron-tempest-plugin-dynamic-routing
+        - neutron-tempest-plugin-fwaas