Merge "Add retry decorator to SSH "execute" method"
diff --git a/.zuul.yaml b/.zuul.yaml
index 17c9e95..011bfe5 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -404,6 +404,129 @@
         cinder: true
 
 - job:
+    name: neutron-tempest-plugin-scenario-openvswitch
+    parent: neutron-tempest-plugin-scenario
+    timeout: 10000
+    vars:
+      network_api_extensions: *api_extensions_master
+      devstack_localrc:
+        Q_AGENT: openvswitch
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        post-config:
+          $NEUTRON_CONF:
+            DEFAULT:
+              enable_dvr: false
+          # NOTE(slaweq): We can get rid of this hardcoded absolute path when
+          # devstack-tempest job will be switched to use lib/neutron instead of
+          # lib/neutron-legacy
+          /$NEUTRON_CORE_PLUGIN_CONF:
+            agent:
+              tunnel_types: vxlan,gre
+            ovs:
+              tunnel_bridge: br-tun
+              bridge_mappings: public:br-ex
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              available_type_drivers: flat,vlan,local,vxlan
+
+- job:
+    name: neutron-tempest-plugin-scenario-openvswitch-queens
+    parent: neutron-tempest-plugin-scenario-openvswitch
+    nodeset: openstack-single-node-xenial
+    override-checkout: stable/queens
+    vars:
+      branch_override: stable/queens
+      network_api_extensions: *api_extensions_queens
+      # TODO(slaweq): remove trunks subport_connectivity test from blacklist
+      # when bug https://bugs.launchpad.net/neutron/+bug/1838760 will be fixed
+      tempest_black_regex: "(^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_subport_connectivity)"
+      devstack_localrc:
+        USE_PYTHON3: false
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+
+- job:
+    name: neutron-tempest-plugin-scenario-openvswitch-rocky
+    parent: neutron-tempest-plugin-scenario-openvswitch
+    nodeset: openstack-single-node-xenial
+    override-checkout: stable/rocky
+    vars:
+      branch_override: stable/rocky
+      network_api_extensions: *api_extensions_rocky
+      devstack_localrc:
+        USE_PYTHON3: false
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+
+- job:
+    name: neutron-tempest-plugin-scenario-openvswitch-stein
+    parent: neutron-tempest-plugin-scenario-openvswitch
+    override-checkout: stable/stein
+    vars:
+      branch_override: stable/stein
+      network_api_extensions: *api_extensions_stein
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+
+- job:
+    name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
+    parent: neutron-tempest-plugin-scenario
+    timeout: 10000
+    vars:
+      network_api_extensions: *api_extensions_master
+      # TODO(slaweq): remove trunks subport_connectivity test from blacklist
+      # when bug https://bugs.launchpad.net/neutron/+bug/1838760 will be fixed
+      tempest_black_regex: "(^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_subport_connectivity)"
+      devstack_localrc:
+        Q_AGENT: openvswitch
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        post-config:
+          $NEUTRON_CONF:
+            DEFAULT:
+              enable_dvr: false
+          # NOTE(slaweq): We can get rid of this hardcoded absolute path when
+          # devstack-tempest job will be switched to use lib/neutron instead of
+          # lib/neutron-legacy
+          /$NEUTRON_CORE_PLUGIN_CONF:
+            agent:
+              tunnel_types: vxlan,gre
+            ovs:
+              tunnel_bridge: br-tun
+              bridge_mappings: public:br-ex
+            securitygroup:
+              firewall_driver: iptables_hybrid
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_plugin_options:
+              available_type_drivers: flat,vlan,local,vxlan
+
+- job:
+    name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-rocky
+    parent: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
+    nodeset: openstack-single-node-xenial
+    override-checkout: stable/rocky
+    vars:
+      branch_override: stable/rocky
+      network_api_extensions: *api_extensions_rocky
+      devstack_localrc:
+        USE_PYTHON3: false
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+
+- job:
+    name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-stein
+    parent: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
+    override-checkout: stable/stein
+    vars:
+      branch_override: stable/stein
+      network_api_extensions: *api_extensions_stein
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+
+- job:
     name: neutron-tempest-plugin-scenario-linuxbridge
     parent: neutron-tempest-plugin-scenario
     timeout: 10000
@@ -779,10 +902,14 @@
         - neutron-tempest-plugin-designate-scenario
         - neutron-tempest-plugin-dvr-multinode-scenario
         - neutron-tempest-plugin-scenario-linuxbridge
+        - neutron-tempest-plugin-scenario-openvswitch
+        - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
     gate:
       jobs:
         - neutron-tempest-plugin-api
         - neutron-tempest-plugin-scenario-linuxbridge
+        - neutron-tempest-plugin-scenario-openvswitch
+        - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
 
 - project-template:
     name: neutron-tempest-plugin-jobs-queens
@@ -792,6 +919,7 @@
         - neutron-tempest-plugin-designate-scenario-queens
         - neutron-tempest-plugin-dvr-multinode-scenario-queens
         - neutron-tempest-plugin-scenario-linuxbridge-queens
+        - neutron-tempest-plugin-scenario-openvswitch-queens
     gate:
       jobs:
         - neutron-tempest-plugin-api-queens
@@ -804,6 +932,8 @@
         - neutron-tempest-plugin-designate-scenario-rocky
         - neutron-tempest-plugin-dvr-multinode-scenario-rocky
         - neutron-tempest-plugin-scenario-linuxbridge-rocky
+        - neutron-tempest-plugin-scenario-openvswitch-rocky
+        - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-rocky
     gate:
       jobs:
         - neutron-tempest-plugin-api-rocky
@@ -816,6 +946,8 @@
         - neutron-tempest-plugin-designate-scenario-stein
         - neutron-tempest-plugin-dvr-multinode-scenario-stein
         - neutron-tempest-plugin-scenario-linuxbridge-stein
+        - neutron-tempest-plugin-scenario-openvswitch-stein
+        - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-stein
     gate:
       jobs:
         - neutron-tempest-plugin-api-stein
diff --git a/neutron_tempest_plugin/api/admin/test_routers_dvr.py b/neutron_tempest_plugin/api/admin/test_routers_dvr.py
index 8d80ba6..ab25a3f 100644
--- a/neutron_tempest_plugin/api/admin/test_routers_dvr.py
+++ b/neutron_tempest_plugin/api/admin/test_routers_dvr.py
@@ -55,10 +55,8 @@
         set to True
         """
         name = data_utils.rand_name('router')
-        router = self.admin_client.create_router(name, distributed=True)
-        self.addCleanup(self.admin_client.delete_router,
-                        router['router']['id'])
-        self.assertTrue(router['router']['distributed'])
+        router = self._create_admin_router(name, distributed=True)
+        self.assertTrue(router['distributed'])
 
     @decorators.idempotent_id('8a0a72b4-7290-4677-afeb-b4ffe37bc352')
     def test_centralized_router_creation(self):
@@ -74,10 +72,8 @@
         as opposed to a "Distributed Virtual Router"
         """
         name = data_utils.rand_name('router')
-        router = self.admin_client.create_router(name, distributed=False)
-        self.addCleanup(self.admin_client.delete_router,
-                        router['router']['id'])
-        self.assertFalse(router['router']['distributed'])
+        router = self._create_admin_router(name, distributed=False)
+        self.assertFalse(router['distributed'])
 
 
 class RouterTestCentralizedToDVR(RoutersTestDVRBase):
@@ -100,13 +96,10 @@
         """
         name = data_utils.rand_name('router')
         # router needs to be in admin state down in order to be upgraded to DVR
-        router = self.admin_client.create_router(name, distributed=False,
-                                                 ha=False,
-                                                 admin_state_up=False)
-        self.addCleanup(self.admin_client.delete_router,
-                        router['router']['id'])
-        self.assertFalse(router['router']['distributed'])
-        self.assertFalse(router['router']['ha'])
-        router = self.admin_client.update_router(router['router']['id'],
+        router = self._create_admin_router(name, distributed=False,
+                                           ha=False, admin_state_up=False)
+        self.assertFalse(router['distributed'])
+        self.assertFalse(router['ha'])
+        router = self.admin_client.update_router(router['id'],
                                                  distributed=True)
         self.assertTrue(router['router']['distributed'])
diff --git a/neutron_tempest_plugin/api/test_routers.py b/neutron_tempest_plugin/api/test_routers.py
index ab6b0f6..3b9867b 100644
--- a/neutron_tempest_plugin/api/test_routers.py
+++ b/neutron_tempest_plugin/api/test_routers.py
@@ -73,12 +73,11 @@
             external_gateway_info = {
                 'network_id': CONF.network.public_network_id,
                 'enable_snat': enable_snat}
-            create_body = self.admin_client.create_router(
-                name, external_gateway_info=external_gateway_info)
-            self.addCleanup(self.admin_client.delete_router,
-                            create_body['router']['id'])
+            router = self._create_admin_router(
+                name, external_network_id=CONF.network.public_network_id,
+                enable_snat=enable_snat)
             # Verify snat attributes after router creation
-            self._verify_router_gateway(create_body['router']['id'],
+            self._verify_router_gateway(router['id'],
                                         exp_ext_gw_info=external_gateway_info)
 
     def _verify_router_gateway(self, router_id, exp_ext_gw_info=None):
@@ -238,12 +237,8 @@
     @decorators.idempotent_id('141297aa-3424-455d-aa8d-f2d95731e00a')
     def test_create_distributed_router(self):
         name = data_utils.rand_name('router')
-        create_body = self.admin_client.create_router(
-            name, distributed=True)
-        self.addCleanup(self._delete_router,
-                        create_body['router']['id'],
-                        self.admin_client)
-        self.assertTrue(create_body['router']['distributed'])
+        router = self._create_admin_router(name, distributed=True)
+        self.assertTrue(router['distributed'])
 
 
 class DvrRoutersTestToCentralized(base_routers.BaseRouterTest):
@@ -255,11 +250,9 @@
         # Convert a centralized router to distributed firstly
         router_args = {'tenant_id': self.client.tenant_id,
                        'distributed': False, 'ha': False}
-        router = self.admin_client.create_router(
+        router = self._create_admin_router(
             data_utils.rand_name('router'), admin_state_up=False,
-            **router_args)['router']
-        self.addCleanup(self.admin_client.delete_router,
-                        router['id'])
+            **router_args)
         self.assertFalse(router['distributed'])
         self.assertFalse(router['ha'])
         update_body = self.admin_client.update_router(router['id'],
@@ -289,11 +282,9 @@
     def test_convert_centralized_router_to_distributed_extended(self):
         router_args = {'tenant_id': self.client.tenant_id,
                        'distributed': False, 'ha': False}
-        router = self.admin_client.create_router(
+        router = self._create_admin_router(
             data_utils.rand_name('router'), admin_state_up=True,
-            **router_args)['router']
-        self.addCleanup(self.admin_client.delete_router,
-                        router['id'])
+            **router_args)
         self.assertTrue(router['admin_state_up'])
         self.assertFalse(router['distributed'])
         # take router down to allow setting the router to distributed
@@ -314,11 +305,9 @@
     def test_convert_distributed_router_to_centralized_extended(self):
         router_args = {'tenant_id': self.client.tenant_id,
                        'distributed': True, 'ha': False}
-        router = self.admin_client.create_router(
+        router = self._create_admin_router(
             data_utils.rand_name('router'), admin_state_up=True,
-            **router_args)['router']
-        self.addCleanup(self.admin_client.delete_router,
-                        router['id'])
+            **router_args)
         self.assertTrue(router['admin_state_up'])
         self.assertTrue(router['distributed'])
         # take router down to allow setting the router to centralized
diff --git a/neutron_tempest_plugin/api/test_routers_negative.py b/neutron_tempest_plugin/api/test_routers_negative.py
index f085fc9..5f24732 100644
--- a/neutron_tempest_plugin/api/test_routers_negative.py
+++ b/neutron_tempest_plugin/api/test_routers_negative.py
@@ -90,9 +90,9 @@
         # create a centralized router
         router_args = {'tenant_id': self.client.tenant_id,
                        'distributed': False}
-        router = self.admin_client.create_router(
+        router = self._create_admin_router(
             data_utils.rand_name('router'), admin_state_up=True,
-            **router_args)['router']
+            **router_args)
         self.assertTrue(router['admin_state_up'])
         self.assertFalse(router['distributed'])
         # attempt to set the router to distributed, catch BadRequest exception
@@ -107,9 +107,9 @@
         # create a centralized router
         router_args = {'tenant_id': self.client.tenant_id,
                        'distributed': False}
-        router = self.admin_client.create_router(
+        router = self._create_admin_router(
             data_utils.rand_name('router'), admin_state_up=True,
-            **router_args)['router']
+            **router_args)
         self.assertTrue(router['admin_state_up'])
         self.assertFalse(router['distributed'])
         # take the router down to modify distributed->True
diff --git a/neutron_tempest_plugin/fwaas/common/fwaas_v2_client.py b/neutron_tempest_plugin/fwaas/common/fwaas_v2_client.py
index 767afc0..44b0952 100644
--- a/neutron_tempest_plugin/fwaas/common/fwaas_v2_client.py
+++ b/neutron_tempest_plugin/fwaas/common/fwaas_v2_client.py
@@ -136,7 +136,7 @@
                                         nl_constants.PENDING_UPDATE])
 
     def _wait_firewall_group_while(self, firewall_group_id, statuses,
-        not_found_ok=False):
+                                   not_found_ok=False):
         start = int(time.time())
         if not_found_ok:
             expected_exceptions = (lib_exc.NotFound)
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index d87a365..4b2ddcd 100644
--- a/neutron_tempest_plugin/scenario/base.py
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -170,8 +170,8 @@
         client = client or cls.os_primary.interfaces_client
         client.delete_interface(server_id, port_id=port_id)
 
-    def setup_network_and_server(
-        self, router=None, server_name=None, network=None, **kwargs):
+    def setup_network_and_server(self, router=None, server_name=None,
+                                 network=None, **kwargs):
         """Create network resources and a server.
 
         Creating a network, subnet, router, keypair, security group
diff --git a/neutron_tempest_plugin/scenario/test_multicast.py b/neutron_tempest_plugin/scenario/test_multicast.py
index cfaa73f..9b79582 100644
--- a/neutron_tempest_plugin/scenario/test_multicast.py
+++ b/neutron_tempest_plugin/scenario/test_multicast.py
@@ -22,11 +22,13 @@
 from neutron_tempest_plugin.common import ssh
 from neutron_tempest_plugin.common import utils
 from neutron_tempest_plugin import config
+from neutron_tempest_plugin import exceptions
 from neutron_tempest_plugin.scenario import base
 
 
 CONF = config.CONF
 LOG = log.getLogger(__name__)
+PYTHON3_BIN = "python3"
 
 
 def get_receiver_script(group, port, hello_message, ack_message, result_file):
@@ -192,20 +194,27 @@
         port = self.client.list_ports(
             network_id=self.network['id'], device_id=server['id'])['ports'][0]
         server['fip'] = self.create_floatingip(port=port)
+        server['ssh_client'] = ssh.Client(server['fip']['floating_ip_address'],
+                                          self.username,
+                                          pkey=self.keypair['private_key'])
+        self._check_python_installed_on_server(server['ssh_client'],
+                                               server['id'])
         return server
 
+    def _check_python_installed_on_server(self, ssh_client, server_id):
+        try:
+            ssh_client.execute_script('which %s' % PYTHON3_BIN)
+        except exceptions.SSHScriptFailed:
+            raise self.skipException(
+                "%s is not available on server %s" % (PYTHON3_BIN, server_id))
+
     def _prepare_sender(self, server, mcast_address):
         check_script = get_sender_script(
             group=mcast_address, port=self.multicast_port,
             message=self.multicast_message,
             result_file=self.sender_output_file)
-        ssh_client = ssh.Client(server['fip']['floating_ip_address'],
-                                self.username,
-                                pkey=self.keypair['private_key'])
-
-        ssh_client.execute_script(
+        server['ssh_client'].execute_script(
             'echo "%s" > ~/multicast_traffic_sender.py' % check_script)
-        return ssh_client
 
     def _prepare_receiver(self, server, mcast_address):
         check_script = get_receiver_script(
@@ -216,9 +225,9 @@
             server['fip']['floating_ip_address'],
             self.username,
             pkey=self.keypair['private_key'])
-        ssh_client.execute_script(
+        self._check_python_installed_on_server(ssh_client, server['id'])
+        server['ssh_client'].execute_script(
             'echo "%s" > ~/multicast_traffic_receiver.py' % check_script)
-        return ssh_client
 
     @decorators.idempotent_id('113486fc-24c9-4be4-8361-03b1c9892867')
     def test_multicast_between_vms_on_same_network(self):
@@ -246,41 +255,39 @@
                     path=file_path))
             return msg in result
 
-        sender_ssh_client = self._prepare_sender(sender, mcast_address)
-        receiver_ssh_clients = []
+        self._prepare_sender(sender, mcast_address)
         receiver_ids = []
         for receiver in receivers:
-            receiver_ssh_client = self._prepare_receiver(
-                receiver, mcast_address)
-            receiver_ssh_client.execute_script(
-                "python3 ~/multicast_traffic_receiver.py &", shell="bash")
+            self._prepare_receiver(receiver, mcast_address)
+            receiver['ssh_client'].execute_script(
+                "%s ~/multicast_traffic_receiver.py &" % PYTHON3_BIN,
+                shell="bash")
             utils.wait_until_true(
                 lambda: _message_received(
-                    receiver_ssh_client, self.hello_message,
+                    receiver['ssh_client'], self.hello_message,
                     self.receiver_output_file),
                 exception=RuntimeError(
                     "Receiver script didn't start properly on server "
                     "{!r}.".format(receiver['id'])))
 
-            receiver_ssh_clients.append(receiver_ssh_client)
             receiver_ids.append(receiver['id'])
 
         # Now lets run scripts on sender
-        sender_ssh_client.execute_script(
-            "python3 ~/multicast_traffic_sender.py")
+        sender['ssh_client'].execute_script(
+            "%s ~/multicast_traffic_sender.py" % PYTHON3_BIN)
 
         # And check if message was received
-        for receiver_ssh_client in receiver_ssh_clients:
+        for receiver in receivers:
             utils.wait_until_true(
                 lambda: _message_received(
-                    receiver_ssh_client, self.multicast_message,
+                    receiver['ssh_client'], self.multicast_message,
                     self.receiver_output_file),
                 exception=RuntimeError(
                     "Receiver {!r} didn't get multicast message".format(
                         receiver['id'])))
 
         # TODO(slaweq): add validation of answears on sended server
-        replies_result = sender_ssh_client.execute_script(
+        replies_result = sender['ssh_client'].execute_script(
             "cat {path} || echo '{path} not exists yet'".format(
                 path=self.sender_output_file))
         for receiver_id in receiver_ids:
diff --git a/tox.ini b/tox.ini
index daf728a..bba37bb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -55,12 +55,11 @@
 commands = oslo_debug_helper -t neutron_tempest_plugin/ {posargs}
 
 [flake8]
-# E125 continuation line does not distinguish itself from next logical line
 # E126 continuation line over-indented for hanging indent
 # E128 continuation line under-indented for visual indent
 # E129 visually indented line with same indent as next logical line
 # N530 direct neutron imports not allowed
-ignore = E125,E126,E128,E129,N530
+ignore = E126,E128,E129,N530
 # H106: Don't put vim configuration in source files
 # H203: Use assertIs(Not)None to check for None
 # H204: Use assert(Not)Equal to check for equality