Merge "Adds get_amphora_stats to the service client"
diff --git a/octavia_tempest_plugin/tests/scenario/v2/test_traffic_ops.py b/octavia_tempest_plugin/tests/scenario/v2/test_traffic_ops.py
index 627c261..edc2cbc 100644
--- a/octavia_tempest_plugin/tests/scenario/v2/test_traffic_ops.py
+++ b/octavia_tempest_plugin/tests/scenario/v2/test_traffic_ops.py
@@ -620,3 +620,66 @@
         self.assertConsistentResponse((403, None),
                                       url_for_member1,
                                       headers={'reject': 'true'})
+
+    @testtools.skipIf(CONF.load_balancer.test_with_noop,
+                      'Traffic tests will not work in noop mode.')
+    @testtools.skipUnless(CONF.load_balancer.test_with_ipv6,
+                          'Mixed IPv4/IPv6 member test requires IPv6.')
+    @decorators.idempotent_id('20b6b671-0101-4bed-a249-9af6ee3aa6d9')
+    def test_mixed_ipv4_ipv6_members_traffic(self):
+        """Tests traffic through a loadbalancer with IPv4 and IPv6 members.
+
+        * Set up members on a loadbalancer.
+        * Test traffic to ensure it is balanced properly.
+        """
+        # Set up Member 1 for Webserver 1
+        member1_name = data_utils.rand_name("lb_member_member1-traffic")
+        member1_kwargs = {
+            const.POOL_ID: self.pool_id,
+            const.NAME: member1_name,
+            const.ADMIN_STATE_UP: True,
+            const.ADDRESS: self.webserver1_ip,
+            const.PROTOCOL_PORT: 80,
+        }
+        if self.lb_member_1_subnet:
+            member1_kwargs[const.SUBNET_ID] = self.lb_member_1_subnet[const.ID]
+
+        member1 = self.mem_member_client.create_member(
+            **member1_kwargs)
+        self.addCleanup(
+            self.mem_member_client.cleanup_member,
+            member1[const.ID], pool_id=self.pool_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+        waiters.wait_for_status(
+            self.mem_lb_client.show_loadbalancer, self.lb_id,
+            const.PROVISIONING_STATUS, const.ACTIVE,
+            CONF.load_balancer.check_interval,
+            CONF.load_balancer.check_timeout)
+
+        # Set up Member 2 for Webserver 2
+        member2_name = data_utils.rand_name("lb_member_member2-traffic")
+        member2_kwargs = {
+            const.POOL_ID: self.pool_id,
+            const.NAME: member2_name,
+            const.ADMIN_STATE_UP: True,
+            const.ADDRESS: self.webserver2_ipv6,
+            const.PROTOCOL_PORT: 80,
+        }
+        if self.lb_member_2_ipv6_subnet:
+            member2_kwargs[const.SUBNET_ID] = (
+                self.lb_member_2_ipv6_subnet[const.ID])
+
+        member2 = self.mem_member_client.create_member(
+            **member2_kwargs)
+        self.addCleanup(
+            self.mem_member_client.cleanup_member,
+            member2[const.ID], pool_id=self.pool_id,
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
+        waiters.wait_for_status(
+            self.mem_lb_client.show_loadbalancer, self.lb_id,
+            const.PROVISIONING_STATUS, const.ACTIVE,
+            CONF.load_balancer.check_interval,
+            CONF.load_balancer.check_timeout)
+
+        # Send some traffic
+        self.check_members_balanced(self.lb_vip_address)
diff --git a/octavia_tempest_plugin/tests/test_base.py b/octavia_tempest_plugin/tests/test_base.py
index c70c2f1..085c814 100644
--- a/octavia_tempest_plugin/tests/test_base.py
+++ b/octavia_tempest_plugin/tests/test_base.py
@@ -75,7 +75,7 @@
 
         for service, available in service_list.items():
             if not available:
-                skip_msg = ("{0} skipped as {1} serivce is not "
+                skip_msg = ("{0} skipped as {1} service is not "
                             "available.".format(cls.__name__, service))
                 raise cls.skipException(skip_msg)
 
@@ -305,6 +305,10 @@
                 'cidr': CONF.load_balancer.member_1_ipv6_subnet_cidr,
                 'ip_version': 6}
             result = cls.lb_mem_subnet_client.create_subnet(**subnet_kwargs)
+            cls.lb_member_1_subnet_prefix = (
+                CONF.load_balancer.member_1_ipv6_subnet_cidr.rpartition('/')[2]
+                )
+            assert(cls.lb_member_1_subnet_prefix.isdigit())
             cls.lb_member_1_ipv6_subnet = result['subnet']
             LOG.info('lb_member_1_ipv6_subnet: {}'.format(
                 cls.lb_member_1_ipv6_subnet))
@@ -354,6 +358,10 @@
                 'cidr': CONF.load_balancer.member_2_ipv6_subnet_cidr,
                 'ip_version': 6}
             result = cls.lb_mem_subnet_client.create_subnet(**subnet_kwargs)
+            cls.lb_member_2_subnet_prefix = (
+                CONF.load_balancer.member_2_ipv6_subnet_cidr.rpartition('/')[2]
+                )
+            assert(cls.lb_member_2_subnet_prefix.isdigit())
             cls.lb_member_2_ipv6_subnet = result['subnet']
             LOG.info('lb_member_2_ipv6_subnet: {}'.format(
                 cls.lb_member_2_ipv6_subnet))
@@ -520,6 +528,17 @@
         LOG.debug('Octavia Setup: webserver2_public_ip = {}'.format(
             cls.webserver2_public_ip))
 
+        if CONF.load_balancer.test_with_ipv6:
+            # Enable the IPv6 nic in webserver 1
+            cls._enable_ipv6_nic_webserver(
+                cls.webserver1_public_ip, cls.lb_member_keypair['private_key'],
+                cls.webserver1_ipv6, cls.lb_member_1_subnet_prefix)
+
+            # Enable the IPv6 nic in webserver 2
+            cls._enable_ipv6_nic_webserver(
+                cls.webserver2_public_ip, cls.lb_member_keypair['private_key'],
+                cls.webserver2_ipv6, cls.lb_member_2_subnet_prefix)
+
         # Set up serving on webserver 1
         cls._install_start_webserver(cls.webserver1_public_ip,
                                      cls.lb_member_keypair['private_key'],
@@ -710,6 +729,19 @@
         linux_client.exec_command('sudo screen -d -m {0} -port 81 '
                                   '-id {1}'.format(dest_file, start_id + 1))
 
+    # Cirros does not configure the assigned IPv6 address by default
+    # so enable it manually like tempest does here:
+    # tempest/scenario/test_netowrk_v6.py turn_nic6_on()
+    @classmethod
+    def _enable_ipv6_nic_webserver(cls, ip_address, ssh_key,
+                                   ipv6_address, ipv6_prefix):
+        linux_client = remote_client.RemoteClient(
+            ip_address, CONF.validation.image_ssh_user, pkey=ssh_key)
+        linux_client.validate_authentication()
+
+        linux_client.exec_command('sudo ip address add {0}/{1} dev '
+                                  'eth0'.format(ipv6_address, ipv6_prefix))
+
     @classmethod
     def _validate_webserver(cls, ip_address, start_id):
         URL = 'http://{0}'.format(ip_address)
@@ -774,6 +806,8 @@
         :param cookies: Optional cookies to send in the request.
         :param redirect: Is the request a redirect? If true, assume the passed
                          content should be the next URL in the chain.
+        :param timeout: Optional seconds to wait for the server to send data.
+
         :return: boolean success status
 
         :raises: testtools.matchers.MismatchError
@@ -788,6 +822,7 @@
                 self.assertEqual(response_code, req.status_code)
             if redirect:
                 self.assertTrue(req.is_redirect)
-                self.assertEqual(response_content, req.next.url)
+                self.assertEqual(response_content,
+                                 session.get_redirect_target(req))
             elif response_content:
                 self.assertEqual(six.text_type(response_content), req.text)
diff --git a/setup.cfg b/setup.cfg
index 17480f6..5da1d1d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,7 +4,7 @@
 description-file =
     README.rst
 author = OpenStack
-author-email = openstack-dev@lists.openstack.org
+author-email = openstack-discuss@lists.openstack.org
 home-page = https://docs.openstack.org/octavia-tempest-plugin/latest/
 classifier =
     Environment :: OpenStack