Add listener stats service client and API test

This patch adds the listener stats service client and tempest API test.

Change-Id: I008204d5000c9a2fb4852021f0c759a2490c69b7
Story: 2004853
Task: 29079
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/listener_client.py b/octavia_tempest_plugin/services/load_balancer/v2/listener_client.py
index 082f4b7..ce41b0f 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/listener_client.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/listener_client.py
@@ -13,6 +13,8 @@
 #   under the License.
 #
 
+import json
+
 from tempest import config
 
 from octavia_tempest_plugin.services.load_balancer.v2 import base_client
@@ -291,3 +293,53 @@
         """
         return self._delete_obj(obj_id=listener_id,
                                 ignore_errors=ignore_errors)
+
+    def get_listener_stats(self, listener_id, query_params=None,
+                           return_object_only=True):
+        """Get listener statistics.
+
+        :param listener_id: The listener ID to query.
+        :param query_params: The optional query parameters to append to the
+                             request. Ex. fields=id&fields=name
+        :param return_object_only: If True, the response returns the object
+                                   inside the root tag. False returns the full
+                                   response from the API.
+        :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: A listener statistics object.
+        """
+        if query_params:
+            request_uri = '{0}/{1}/stats?{2}'.format(self.uri, listener_id,
+                                                     query_params)
+        else:
+            request_uri = '{0}/{1}/stats'.format(self.uri, listener_id)
+
+        response, body = self.get(request_uri)
+        self.expected_success(200, response.status)
+        if return_object_only:
+            return json.loads(body.decode('utf-8'))['stats']
+        else:
+            return json.loads(body.decode('utf-8'))
diff --git a/octavia_tempest_plugin/tests/api/v2/test_listener.py b/octavia_tempest_plugin/tests/api/v2/test_listener.py
index 38da0ae..b68ae55 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_listener.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_listener.py
@@ -764,3 +764,76 @@
             const.ACTIVE,
             CONF.load_balancer.check_interval,
             CONF.load_balancer.check_timeout)
+
+    @decorators.idempotent_id('6f14a6c1-945e-43bc-8215-410c8a5edb25')
+    def test_listener_show_stats(self):
+        """Tests listener show statistics API.
+
+        * Create a listener.
+        * Validates that other accounts cannot see the stats for the
+        *   listener.
+        * Show listener statistics.
+        * Validate the show reflects the expected values.
+        """
+        listener_name = data_utils.rand_name("lb_member_listener1-stats")
+        listener_description = data_utils.arbitrary_string(size=255)
+
+        listener_kwargs = {
+            const.NAME: listener_name,
+            const.DESCRIPTION: listener_description,
+            const.ADMIN_STATE_UP: True,
+            const.PROTOCOL: const.HTTP,
+            const.PROTOCOL_PORT: 84,
+            const.LOADBALANCER_ID: self.lb_id,
+            const.CONNECTION_LIMIT: 200,
+        }
+
+        listener = self.mem_listener_client.create_listener(**listener_kwargs)
+        self.addCleanup(
+            self.mem_listener_client.cleanup_listener,
+            listener[const.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.build_interval,
+            CONF.load_balancer.build_timeout)
+        listener = waiters.wait_for_status(
+            self.mem_listener_client.show_listener,
+            listener[const.ID], const.PROVISIONING_STATUS,
+            const.ACTIVE,
+            CONF.load_balancer.build_interval,
+            CONF.load_balancer.build_timeout)
+        if not CONF.load_balancer.test_with_noop:
+            listener = waiters.wait_for_status(
+                self.mem_listener_client.show_listener,
+                listener[const.ID], const.OPERATING_STATUS,
+                const.ONLINE,
+                CONF.load_balancer.build_interval,
+                CONF.load_balancer.build_timeout)
+
+        # Test that a user, without the load balancer member role, cannot
+        # use this command
+        if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
+            self.assertRaises(
+                exceptions.Forbidden,
+                self.os_primary.listener_client.get_listener_stats,
+                listener[const.ID])
+
+        # Test that a different user, with the load balancer role, cannot see
+        # the listener stats
+        if not CONF.load_balancer.RBAC_test_type == const.NONE:
+            member2_client = self.os_roles_lb_member2.listener_client
+            self.assertRaises(exceptions.Forbidden,
+                              member2_client.get_listener_stats,
+                              listener[const.ID])
+
+        stats = self.mem_listener_client.get_listener_stats(listener[const.ID])
+
+        self.assertEqual(5, len(stats))
+        self.assertEqual(0, stats[const.ACTIVE_CONNECTIONS])
+        self.assertEqual(0, stats[const.BYTES_IN])
+        self.assertEqual(0, stats[const.BYTES_OUT])
+        self.assertEqual(0, stats[const.REQUEST_ERRORS])
+        self.assertEqual(0, stats[const.TOTAL_CONNECTIONS])
diff --git a/releasenotes/notes/add-listener-stats-api-test-88947cf5e6ae9cae.yaml b/releasenotes/notes/add-listener-stats-api-test-88947cf5e6ae9cae.yaml
new file mode 100644
index 0000000..1737aa2
--- /dev/null
+++ b/releasenotes/notes/add-listener-stats-api-test-88947cf5e6ae9cae.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Added the listener stats service client and API test.