Merge "Increase connection_max_retries to 480 secs on CentOS jobs"
diff --git a/octavia_tempest_plugin/config.py b/octavia_tempest_plugin/config.py
index dee2bd2..52da1f2 100644
--- a/octavia_tempest_plugin/config.py
+++ b/octavia_tempest_plugin/config.py
@@ -115,6 +115,18 @@
                default=const.SINGLE,
                choices=const.SUPPORTED_LB_TOPOLOGIES,
                help='Load balancer topology configuration.'),
+    cfg.DictOpt('expected_flavor_capability',
+                help=('Defines a provider flavor capability that is expected '
+                      'to be present in the selected provider under test. '
+                      'It is specified in a "name": "description" dict. '
+                      'Example: {"loadbalancer_topology": "The load balancer '
+                      'topology. One of: SINGLE - One amphora per load '
+                      'balancer. ACTIVE_STANDBY - Two amphora per load '
+                      'balancer."}'),
+                default={'loadbalancer_topology': 'The load balancer '
+                         'topology. One of: SINGLE - One amphora per load '
+                         'balancer. ACTIVE_STANDBY - Two amphora per load '
+                         'balancer.'}),
     # Networking
     cfg.BoolOpt('test_with_ipv6',
                 default=True,
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/amphora_client.py b/octavia_tempest_plugin/services/load_balancer/v2/amphora_client.py
index ce324cb..4094515 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/amphora_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/amphora_client.py
@@ -157,3 +157,73 @@
             return jsonutils.loads(body.decode('utf-8'))[self.stats_root_tag]
         else:
             return jsonutils.loads(body.decode('utf-8'))
+
+    def update_amphora_config(self, amphora_id):
+        """Update the amphora agent configuration.
+
+        :param amphora_id: The ID of the amphora to update.
+        :raises AssertionError: if the expected_code isn't a valid http success
+                                response code
+        :raises BadRequest: If a 400 response code is received
+        :raises Conflict: If a 409 response code is received
+        :raises Forbidden: If a 403 response code is received
+        :raises Gone: If a 410 response code is received
+        :raises InvalidContentType: If a 415 response code is received
+        :raises InvalidHTTPResponseBody: The response body wasn't valid JSON
+        :raises InvalidHttpSuccessCode: if the read code isn't an expected
+                                        http success code
+        :raises NotFound: If a 404 response code is received
+        :raises NotImplemented: If a 501 response code is received
+        :raises OverLimit: If a 413 response code is received and over_limit is
+                           not in the response body
+        :raises RateLimitExceeded: If a 413 response code is received and
+                                   over_limit is in the response body
+        :raises ServerFault: If a 500 response code is received
+        :raises Unauthorized: If a 401 response code is received
+        :raises UnexpectedContentType: If the content-type of the response
+                                       isn't an expect type
+        :raises UnexpectedResponseCode: If a response code above 400 is
+                                        received and it doesn't fall into any
+                                        of the handled checks
+        :raises UnprocessableEntity: If a 422 response code is received and
+                                     couldn't be parsed
+        :returns: None
+        """
+        uri = '{0}/{1}/config'.format(self.uri, amphora_id)
+        response, body = self.put(uri, '')
+        self.expected_success(202, response.status)
+
+    def amphora_failover(self, amphora_id):
+        """Failover an amphora.
+
+        :param amphora_id: The ID of the amphora to failover.
+        :raises AssertionError: if the expected_code isn't a valid http success
+                                response code
+        :raises BadRequest: If a 400 response code is received
+        :raises Conflict: If a 409 response code is received
+        :raises Forbidden: If a 403 response code is received
+        :raises Gone: If a 410 response code is received
+        :raises InvalidContentType: If a 415 response code is received
+        :raises InvalidHTTPResponseBody: The response body wasn't valid JSON
+        :raises InvalidHttpSuccessCode: if the read code isn't an expected
+                                        http success code
+        :raises NotFound: If a 404 response code is received
+        :raises NotImplemented: If a 501 response code is received
+        :raises OverLimit: If a 413 response code is received and over_limit is
+                           not in the response body
+        :raises RateLimitExceeded: If a 413 response code is received and
+                                   over_limit is in the response body
+        :raises ServerFault: If a 500 response code is received
+        :raises Unauthorized: If a 401 response code is received
+        :raises UnexpectedContentType: If the content-type of the response
+                                       isn't an expect type
+        :raises UnexpectedResponseCode: If a response code above 400 is
+                                        received and it doesn't fall into any
+                                        of the handled checks
+        :raises UnprocessableEntity: If a 422 response code is received and
+                                     couldn't be parsed
+        :returns: None
+        """
+        uri = '{0}/{1}/failover'.format(self.uri, amphora_id)
+        response, body = self.put(uri, '')
+        self.expected_success(202, response.status)
diff --git a/octavia_tempest_plugin/tests/api/v2/test_amphora.py b/octavia_tempest_plugin/tests/api/v2/test_amphora.py
new file mode 100644
index 0000000..7873679
--- /dev/null
+++ b/octavia_tempest_plugin/tests/api/v2/test_amphora.py
@@ -0,0 +1,134 @@
+#    Copyright 2019 Rackspace US Inc.  All rights reserved.
+#
+#    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.
+
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions
+
+from octavia_tempest_plugin.common import constants as const
+from octavia_tempest_plugin.tests import test_base
+from octavia_tempest_plugin.tests import waiters
+
+CONF = config.CONF
+
+
+class AmphoraAPITest(test_base.LoadBalancerBaseTest):
+    """Test the amphora object API."""
+
+    @classmethod
+    def skip_checks(cls):
+        super(AmphoraAPITest, cls).skip_checks()
+        if CONF.load_balancer.provider not in ['amphora', 'octavia']:
+            raise cls.skipException('Amphora tests only run with the amphora '
+                                    'provider enabled.')
+
+    @classmethod
+    def resource_setup(cls):
+        """Setup resources needed by the tests."""
+        super(AmphoraAPITest, cls).resource_setup()
+
+        lb_name = data_utils.rand_name("lb_member_lb1-amphora-api")
+        lb_kwargs = {const.PROVIDER: CONF.load_balancer.provider,
+                     const.NAME: lb_name}
+
+        cls._setup_lb_network_kwargs(lb_kwargs)
+
+        lb = cls.mem_lb_client.create_loadbalancer(**lb_kwargs)
+        cls.lb_id = lb[const.ID]
+        cls.addClassResourceCleanup(
+            cls.mem_lb_client.cleanup_loadbalancer,
+            cls.lb_id)
+
+        waiters.wait_for_status(cls.mem_lb_client.show_loadbalancer,
+                                cls.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.lb_build_interval,
+                                CONF.load_balancer.lb_build_timeout)
+
+    @decorators.idempotent_id('b7fc231b-dcfa-47a5-99f3-ec5ddcc48f30')
+    def test_amphora_update(self):
+        """Tests the amphora agent configuration update API
+
+        * Tests that users without the loadbalancer admin role cannot
+          update an amphora.
+        * Update the amphora.
+        """
+
+        # We have to do this here as the api_version and clients are not
+        # setup in time to use a decorator or the skip_checks mixin
+        if not self.lb_admin_amphora_client.is_version_supported(
+                self.api_version, '2.7'):
+            raise self.skipException('Amphora update is only available on '
+                                     'Octavia API version 2.7 or newer.')
+
+        amphorae = self.lb_admin_amphora_client.list_amphorae(
+            query_params='{loadbalancer_id}={lb_id}'.format(
+                loadbalancer_id=const.LOADBALANCER_ID, lb_id=self.lb_id))
+        amphora_1 = amphorae[0]
+
+        # Test that a user without the load balancer admin role cannot
+        # create a flavor
+        if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
+            self.assertRaises(
+                exceptions.Forbidden,
+                self.os_primary.amphora_client.update_amphora_config,
+                amphora_1[const.ID])
+
+        self.lb_admin_amphora_client.update_amphora_config(amphora_1[const.ID])
+
+        # TODO(johnsom) Assert that an amphora config setting updated
+        #               when we have a setting to check.
+
+        amp = self.lb_admin_amphora_client.show_amphora(amphora_1[const.ID])
+
+        self.assertEqual(const.STATUS_ALLOCATED, amp[const.STATUS])
+
+    @decorators.idempotent_id('fb772680-b2ba-4fc3-989b-95ad8492ccaf')
+    def test_amphora_failover(self):
+        """Tests the amphora failover API.
+
+        * Validates that non-admin accounts cannot failover amphora
+        * Fails over an amphora
+        * Validates that a new amphora is built
+        """
+        amphorae = self.lb_admin_amphora_client.list_amphorae(
+            query_params='{loadbalancer_id}={lb_id}'.format(
+                loadbalancer_id=const.LOADBALANCER_ID, lb_id=self.lb_id))
+        amphora_1 = amphorae[0]
+
+        # Test RBAC not authorized for non-admin role
+        if not CONF.load_balancer.RBAC_test_type == const.NONE:
+            self.assertRaises(exceptions.Forbidden,
+                              self.os_primary.amphora_client.amphora_failover,
+                              amphora_1[const.ID])
+            self.assertRaises(
+                exceptions.Forbidden,
+                self.os_roles_lb_member.amphora_client.amphora_failover,
+                amphora_1[const.ID])
+
+        self.lb_admin_amphora_client.amphora_failover(amphora_1[const.ID])
+
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                self.lb_id, const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.lb_build_interval,
+                                CONF.load_balancer.lb_build_timeout)
+
+        after_amphorae = self.lb_admin_amphora_client.list_amphorae(
+            query_params='{loadbalancer_id}={lb_id}'.format(
+                loadbalancer_id=const.LOADBALANCER_ID, lb_id=self.lb_id))
+
+        for new_amp in after_amphorae:
+            self.assertNotEqual(amphora_1[const.ID], new_amp[const.ID])
diff --git a/octavia_tempest_plugin/tests/api/v2/test_flavor_capabilities.py b/octavia_tempest_plugin/tests/api/v2/test_flavor_capabilities.py
new file mode 100644
index 0000000..924c044
--- /dev/null
+++ b/octavia_tempest_plugin/tests/api/v2/test_flavor_capabilities.py
@@ -0,0 +1,83 @@
+#    Copyright 2019 Rackspace US Inc.  All rights reserved.
+#
+#    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.
+
+from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions
+
+from octavia_tempest_plugin.common import constants as const
+from octavia_tempest_plugin.tests import test_base
+
+CONF = config.CONF
+
+
+class FlavorCapabilitiesAPITest(test_base.LoadBalancerBaseTest):
+    """Test the provider flavor capabilities API."""
+
+    @decorators.idempotent_id('df837ee3-ca4b-4a4d-a7a3-27fa57cf3a33')
+    def test_flavor_capabilities_list(self):
+        """Tests provider flavor capabilities list API and field filtering.
+
+        * Validates that non-lb admin accounts cannot list the capabilities.
+        * List the flavor capablilities.
+        * Validate that the "loadbalancer_topology" capablility is present.
+        * List the providers returning one field at a time.
+        """
+        # We have to do this here as the api_version and clients are not
+        # setup in time to use a decorator or the skip_checks mixin
+        if not self.mem_provider_client.is_version_supported(
+                self.api_version, '2.6'):
+            raise self.skipException('Flavor capabilities are only available '
+                                     'on Octavia API version 2.6 or newer.')
+
+        # Test that a user without the load balancer admin role cannot
+        # list provider flavor capabilities.
+        if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
+            os_primary_capabilities_client = (
+                self.os_primary.flavor_capabilities_client)
+            self.assertRaises(
+                exceptions.Forbidden,
+                os_primary_capabilities_client.list_flavor_capabilities,
+                CONF.load_balancer.provider)
+
+        # Check for an expected flavor capability for the configured provider
+        admin_capabilities_client = self.lb_admin_capabilities_client
+        capabilities = admin_capabilities_client.list_flavor_capabilities(
+            CONF.load_balancer.provider)
+
+        expected_name = list(
+            CONF.load_balancer.expected_flavor_capability.keys())[0]
+        expected_description = (
+            CONF.load_balancer.expected_flavor_capability[expected_name])
+        for capability in capabilities:
+            if capability[const.NAME] == expected_name:
+                self.assertEqual(expected_description,
+                                 capability[const.DESCRIPTION])
+
+        # Test fields
+        capabilities = admin_capabilities_client.list_flavor_capabilities(
+            CONF.load_balancer.provider,
+            query_params='{fields}={field}&{field}={exp_name}'.format(
+                fields=const.FIELDS, field=const.NAME, exp_name=expected_name))
+        self.assertEqual(1, len(capabilities[0]))
+        self.assertEqual(expected_name, capabilities[0][const.NAME])
+
+        capabilities = admin_capabilities_client.list_flavor_capabilities(
+            CONF.load_balancer.provider,
+            query_params='{fields}={field}&{name}={exp_name}'.format(
+                fields=const.FIELDS, field=const.DESCRIPTION, name=const.NAME,
+                exp_name=expected_name))
+        self.assertEqual(1, len(capabilities[0]))
+        self.assertEqual(expected_description,
+                         capabilities[0][const.DESCRIPTION])
diff --git a/octavia_tempest_plugin/tests/api/v2/test_load_balancer.py b/octavia_tempest_plugin/tests/api/v2/test_load_balancer.py
index 66d26cf..10ae85d 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_load_balancer.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_load_balancer.py
@@ -826,6 +826,11 @@
         lb = self.mem_lb_client.show_loadbalancer(lb[const.ID])
         self.assertEqual(const.ACTIVE, lb[const.PROVISIONING_STATUS])
 
+        if CONF.load_balancer.provider in ['amphora', 'octavia']:
+            before_amphorae = self.lb_admin_amphora_client.list_amphorae(
+                query_params='{loadbalancer_id}={lb_id}'.format(
+                    loadbalancer_id=const.LOADBALANCER_ID, lb_id=lb[const.ID]))
+
         self.os_roles_lb_admin.loadbalancer_client.failover_loadbalancer(
             lb[const.ID])
 
@@ -834,8 +839,17 @@
                                      const.ACTIVE,
                                      CONF.load_balancer.lb_build_interval,
                                      CONF.load_balancer.lb_build_timeout)
-        # TODO(johnsom) Assert the amphora ID has changed when amp client
-        #               is available.
+
+        if CONF.load_balancer.provider in ['amphora', 'octavia']:
+            after_amphorae = self.lb_admin_amphora_client.list_amphorae(
+                query_params='{loadbalancer_id}={lb_id}'.format(
+                    loadbalancer_id=const.LOADBALANCER_ID, lb_id=lb[const.ID]))
+
+            # Make sure all of the amphora on the load balancer have
+            # failed over
+            for amphora in before_amphorae:
+                for new_amp in after_amphorae:
+                    self.assertNotEqual(amphora[const.ID], new_amp[const.ID])
 
         # Attempt to clean up so that one full test run doesn't start 10+
         # amps before the cleanup phase fires
diff --git a/octavia_tempest_plugin/tests/scenario/v2/test_amphora.py b/octavia_tempest_plugin/tests/scenario/v2/test_amphora.py
index b91a368..30a116c 100644
--- a/octavia_tempest_plugin/tests/scenario/v2/test_amphora.py
+++ b/octavia_tempest_plugin/tests/scenario/v2/test_amphora.py
@@ -115,11 +115,11 @@
 
         # Test that a user with cloud admin role can list the amphorae
         if not CONF.load_balancer.RBAC_test_type == const.NONE:
-            adm = self.os_admin.amphora_client.list_amphorae()
+            adm = self.lb_admin_amphora_client.list_amphorae()
             self.assertTrue(len(adm) >= 2 * self._expected_amp_count(adm))
 
         # Get an actual list of the amphorae
-        amphorae = self.os_admin.amphora_client.list_amphorae()
+        amphorae = self.lb_admin_amphora_client.list_amphorae()
 
         # There should be AT LEAST 2, there may be more depending on the
         # configured topology, or if there are other LBs created besides ours
@@ -127,7 +127,7 @@
             len(amphorae) >= 2 * self._expected_amp_count(amphorae))
 
         show_amphora_response_fields = const.SHOW_AMPHORA_RESPONSE_FIELDS
-        if self.mem_amphora_client.is_version_supported(
+        if self.lb_admin_amphora_client.is_version_supported(
                 self.api_version, '2.1'):
             show_amphora_response_fields.append('created_at')
             show_amphora_response_fields.append('updated_at')
@@ -140,7 +140,7 @@
                 self.assertIn(field, amp)
 
             amp_id = amp[const.ID]
-            amp_obj = self.os_admin.amphora_client.show_amphora(
+            amp_obj = self.lb_admin_amphora_client.show_amphora(
                 amphora_id=amp_id)
 
             # Make sure all of the fields exist on the amp show record
@@ -148,7 +148,7 @@
                 self.assertIn(field, amp_obj)
 
             # Verify a few of the fields are the right type
-            if self.mem_amphora_client.is_version_supported(
+            if self.lb_admin_amphora_client.is_version_supported(
                     self.api_version, '2.1'):
                 parser.parse(amp_obj[const.CREATED_AT])
                 parser.parse(amp_obj[const.UPDATED_AT])
@@ -175,13 +175,13 @@
                 self.assertEqual(amp[field], amp_obj[field])
 
         # Test filtering by loadbalancer_id
-        amphorae = self.os_admin.amphora_client.list_amphorae(
+        amphorae = self.lb_admin_amphora_client.list_amphorae(
             query_params='{loadbalancer_id}={lb_id}'.format(
                 loadbalancer_id=const.LOADBALANCER_ID, lb_id=self.lb_id))
         self.assertEqual(self._expected_amp_count(amphorae), len(amphorae))
         self.assertEqual(self.lb_id, amphorae[0][const.LOADBALANCER_ID])
 
-        amphorae = self.os_admin.amphora_client.list_amphorae(
+        amphorae = self.lb_admin_amphora_client.list_amphorae(
             query_params='{loadbalancer_id}={lb_id}'.format(
                 loadbalancer_id=const.LOADBALANCER_ID, lb_id=lb_id))
         self.assertEqual(self._expected_amp_count(amphorae), len(amphorae))
diff --git a/octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py b/octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py
index 79f1876..3d79173 100644
--- a/octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py
+++ b/octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py
@@ -17,6 +17,7 @@
 
 from dateutil import parser
 
+from oslo_serialization import jsonutils
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -30,6 +31,49 @@
 
 class LoadBalancerScenarioTest(test_base.LoadBalancerBaseTest):
 
+    @classmethod
+    def resource_setup(cls):
+        """Setup resources needed by the tests."""
+        super(LoadBalancerScenarioTest, cls).resource_setup()
+
+        if cls.lb_admin_flavor_profile_client.is_version_supported(
+                cls.api_version, '2.6'):
+
+            # Create a shared flavor profile
+            flavor_profile_name = data_utils.rand_name("lb_scenario-setup")
+            flavor_data = {const.LOADBALANCER_TOPOLOGY:
+                           CONF.load_balancer.loadbalancer_topology}
+            flavor_data_json = jsonutils.dumps(flavor_data)
+
+            flavor_profile_kwargs = {
+                const.NAME: flavor_profile_name,
+                const.PROVIDER_NAME: CONF.load_balancer.provider,
+                const.FLAVOR_DATA: flavor_data_json
+            }
+
+            cls.flavor_profile = (
+                cls.lb_admin_flavor_profile_client.create_flavor_profile(
+                    **flavor_profile_kwargs))
+            cls.addClassResourceCleanup(
+                cls.lb_admin_flavor_profile_client.cleanup_flavor_profile,
+                cls.flavor_profile[const.ID])
+
+            flavor_name = data_utils.rand_name("lb_scenario-setup")
+            flavor_description = data_utils.arbitrary_string(size=255)
+
+            flavor_kwargs = {
+                const.NAME: flavor_name,
+                const.DESCRIPTION: flavor_description,
+                const.ENABLED: True,
+                const.FLAVOR_PROFILE_ID: cls.flavor_profile[const.ID]}
+
+            cls.flavor = cls.lb_admin_flavor_client.create_flavor(
+                **flavor_kwargs)
+            cls.addClassResourceCleanup(
+                cls.lb_admin_flavor_client.cleanup_a_flavor,
+                cls.flavor[const.ID])
+            cls.flavor_id = cls.flavor[const.ID]
+
     @decorators.idempotent_id('a5e2e120-4f7e-4c8b-8aac-cf09cb56711c')
     def test_load_balancer_ipv4_CRUD(self):
         self._test_load_balancer_CRUD(4)
@@ -56,6 +100,10 @@
                      const.PROVIDER: CONF.load_balancer.provider,
                      const.NAME: lb_name}
 
+        if self.lb_admin_flavor_profile_client.is_version_supported(
+                self.api_version, '2.6'):
+            lb_kwargs[const.FLAVOR_ID] = self.flavor_id
+
         self._setup_lb_network_kwargs(lb_kwargs, ip_version)
 
         lb = self.mem_lb_client.create_loadbalancer(**lb_kwargs)
diff --git a/octavia_tempest_plugin/tests/test_base.py b/octavia_tempest_plugin/tests/test_base.py
index e85fb0a..2e46812 100644
--- a/octavia_tempest_plugin/tests/test_base.py
+++ b/octavia_tempest_plugin/tests/test_base.py
@@ -121,13 +121,15 @@
             cls.os_roles_lb_member.healthmonitor_client)
         cls.mem_l7policy_client = cls.os_roles_lb_member.l7policy_client
         cls.mem_l7rule_client = cls.os_roles_lb_member.l7rule_client
-        cls.mem_amphora_client = cls.os_roles_lb_member.amphora_client
+        cls.lb_admin_amphora_client = cls.os_roles_lb_admin.amphora_client
         cls.lb_admin_flavor_profile_client = (
             cls.os_roles_lb_admin.flavor_profile_client)
         cls.lb_admin_flavor_client = cls.os_roles_lb_admin.flavor_client
         cls.mem_flavor_client = cls.os_roles_lb_member.flavor_client
         cls.mem_provider_client = cls.os_roles_lb_member.provider_client
         cls.os_admin_servers_client = cls.os_admin.servers_client
+        cls.lb_admin_capabilities_client = (
+            cls.os_roles_lb_admin.flavor_capabilities_client)
 
     @classmethod
     def resource_setup(cls):
diff --git a/requirements.txt b/requirements.txt
index 3b78bc3..8020630 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -17,3 +17,4 @@
 six>=1.10.0 # MIT
 tempest>=17.1.0 # Apache-2.0
 tenacity>=4.4.0 # Apache-2.0
+keystoneauth1>=3.3.0 # Apache-2.0
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
index 2ad1cf5..d342aff 100644
--- a/zuul.d/jobs.yaml
+++ b/zuul.d/jobs.yaml
@@ -293,6 +293,13 @@
       tempest_concurrency: 2
       tempest_test_regex: ^octavia_tempest_plugin.tests.api.v2
       tox_envlist: all
+    irrelevant-files:
+      - ^.*\.rst$
+      - ^api-ref/.*$
+      - ^doc/.*$
+      - ^etc/.*$
+      - ^releasenotes/.*$
+      - ^octavia_tempest_plugin/tests/(?!api/|\w+\.py).*
 
 - job:
     name: octavia-v2-dsvm-noop-py2-api
@@ -328,6 +335,13 @@
       tempest_concurrency: 2
       tempest_test_regex: ^octavia_tempest_plugin.tests.scenario.v2
       tox_envlist: all
+    irrelevant-files:
+      - ^.*\.rst$
+      - ^api-ref/.*$
+      - ^doc/.*$
+      - ^etc/.*$
+      - ^releasenotes/.*$
+      - ^octavia_tempest_plugin/tests/(?!scenario/|\w+\.py).*
 
 - job:
     name: octavia-v2-dsvm-py2-scenario
@@ -351,6 +365,7 @@
     parent: octavia-v2-dsvm-py2-scenario
     override-checkout: stable/queens
 
+# Legacy jobs for the transition to the act-stdby two node jobs
 - job:
     name: octavia-v2-dsvm-scenario-two-node
     parent: octavia-dsvm-live-two-node-base
@@ -358,6 +373,13 @@
       tempest_concurrency: 2
       tempest_test_regex: ^octavia_tempest_plugin.tests.scenario.v2
       tox_envlist: all
+    irrelevant-files:
+      - ^.*\.rst$
+      - ^api-ref/.*$
+      - ^doc/.*$
+      - ^etc/.*$
+      - ^releasenotes/.*$
+      - ^octavia_tempest_plugin/tests/(?!scenario/|\w+\.py).*
 
 - job:
     name: octavia-v2-dsvm-py2-scenario-two-node
@@ -371,6 +393,30 @@
           USE_PYTHON3: False
 
 - job:
+    name: octavia-v2-act-stdby-dsvm-scenario-two-node
+    parent: octavia-dsvm-live-two-node-base
+    vars:
+      tempest_concurrency: 2
+      tempest_test_regex: ^octavia_tempest_plugin.tests.scenario.v2
+      tox_envlist: all
+      devstack_local_conf:
+        test-config:
+          "$TEMPEST_CONFIG":
+            load_balancer:
+              loadbalancer_topology: ACTIVE_STANDBY
+
+- job:
+    name: octavia-v2-act-stdby-dsvm-py2-scenario-two-node
+    parent: octavia-v2-act-stdby-dsvm-scenario-two-node
+    host-vars:
+      controller:
+        devstack_localrc:
+          USE_PYTHON3: False
+      controller2:
+        devstack_localrc:
+          USE_PYTHON3: False
+
+- job:
     name: octavia-v2-dsvm-py2-scenario-centos-7
     parent: octavia-v2-dsvm-py2-scenario
     nodeset: devstack-single-node-centos-7
@@ -419,6 +465,13 @@
         barbican: https://opendev.org/openstack/barbican.git
       devstack_localrc:
         TEMPEST_PLUGINS: '"/opt/stack/octavia-tempest-plugin /opt/stack/barbican-tempest-plugin"'
+    irrelevant-files:
+      - ^.*\.rst$
+      - ^api-ref/.*$
+      - ^doc/.*$
+      - ^etc/.*$
+      - ^releasenotes/.*$
+      - ^octavia_tempest_plugin/tests/(?!barbican_scenario/|\w+\.py).*
 
 - job:
     name: octavia-v2-dsvm-tls-barbican-stable-stein
@@ -512,6 +565,7 @@
       tempest_test_regex: ^octavia_tempest_plugin.tests.act_stdby_scenario.v2.test_active_standby_iptables
       tox_envlist: all
 
+
 - job:
     name: octavia-v2-act-stdby-iptables-dsvm-py2-scenario
     parent: octavia-v2-act-stdby-iptables-dsvm-scenario
diff --git a/zuul.d/projects.yaml b/zuul.d/projects.yaml
index 2bb813e..490d68b 100644
--- a/zuul.d/projects.yaml
+++ b/zuul.d/projects.yaml
@@ -22,9 +22,9 @@
             voting: false
         - octavia-v2-dsvm-scenario-ubuntu-bionic:
             voting: false
-        - octavia-v2-dsvm-scenario-two-node:
+        - octavia-v2-act-stdby-dsvm-scenario-two-node:
             voting: false
-        - octavia-v2-dsvm-py2-scenario-two-node:
+        - octavia-v2-act-stdby-dsvm-py2-scenario-two-node:
             voting: false
         - octavia-v2-act-stdby-iptables-dsvm-scenario:
             voting: false