diff --git a/doc/source/api/octaviaapi.rst b/doc/source/api/octaviaapi.rst index e7fabc2fee..011be3c9c0 100644 --- a/doc/source/api/octaviaapi.rst +++ b/doc/source/api/octaviaapi.rst @@ -607,10 +607,13 @@ Retrieve the stats of a listener. **Response Example**:: { - 'bytes_in': 1000, - 'bytes_out': 1000, - 'active_connections': 1, - 'total_connections': 1 + 'listener': { + 'bytes_in': 1000, + 'bytes_out': 1000, + 'active_connections': 1, + 'total_connections': 1, + 'request_errors': 0 + } } Create Listener diff --git a/octavia/amphorae/backends/health_daemon/health_daemon.py b/octavia/amphorae/backends/health_daemon/health_daemon.py index 068460b642..d48be76b76 100644 --- a/octavia/amphorae/backends/health_daemon/health_daemon.py +++ b/octavia/amphorae/backends/health_daemon/health_daemon.py @@ -83,9 +83,14 @@ def build_stats_message(): SEQ += 1 stat_sock_files = list_sock_stat_files() for listener_id, stat_sock_file in six.iteritems(stat_sock_files): - listener_dict = {'pools': {}, 'status': 'DOWN', - 'stats': {'tx': 0, 'rx': 0, - 'conns': 0, 'totconns': 0}} + listener_dict = {'pools': {}, + 'status': 'DOWN', + 'stats': { + 'tx': 0, + 'rx': 0, + 'conns': 0, + 'totconns': 0, + 'ereq': 0}} msg['listeners'][listener_id] = listener_dict if util.is_listener_running(listener_id): (stats, pool_status) = get_stats(stat_sock_file) @@ -96,6 +101,7 @@ def build_stats_message(): listener_dict['stats']['rx'] = int(row['bin']) listener_dict['stats']['conns'] = int(row['scur']) listener_dict['stats']['totconns'] = int(row['stot']) + listener_dict['stats']['ereq'] = int(row['ereq']) listener_dict['status'] = row['status'] for oid, pool in six.iteritems(pool_status): if oid != listener_id: diff --git a/octavia/api/v1/controllers/listener_statistics.py b/octavia/api/v1/controllers/listener_statistics.py index 3f82739e59..6263dadd18 100644 --- a/octavia/api/v1/controllers/listener_statistics.py +++ b/octavia/api/v1/controllers/listener_statistics.py @@ -15,10 +15,12 @@ import logging import pecan +from wsme import types as wtypes from wsmeext import pecan as wsme_pecan from octavia.api.v1.controllers import base from octavia.api.v1.types import listener_statistics as ls_types +from octavia.common import constants from octavia.common import data_models from octavia.common import exceptions from octavia.i18n import _LI @@ -45,7 +47,7 @@ class ListenerStatisticsController(base.BaseController): id=self.listener_id) return db_ls - @wsme_pecan.wsexpose(ls_types.ListenerStatisticsResponse) + @wsme_pecan.wsexpose({wtypes.text: ls_types.ListenerStatisticsResponse}) def get_all(self): """Gets a single listener's statistics details.""" # NOTE(sbalukoff): since a listener can only have one set of @@ -53,5 +55,5 @@ class ListenerStatisticsController(base.BaseController): # the single set of stats context = pecan.request.context.get('octavia_context') db_ls = self._get_db_ls(context.session) - return self._convert_db_to_type(db_ls, - ls_types.ListenerStatisticsResponse) + return {constants.LISTENER: self._convert_db_to_type( + db_ls, ls_types.ListenerStatisticsResponse)} diff --git a/octavia/api/v1/types/listener_statistics.py b/octavia/api/v1/types/listener_statistics.py index dad27e089a..d31601f56c 100644 --- a/octavia/api/v1/types/listener_statistics.py +++ b/octavia/api/v1/types/listener_statistics.py @@ -18,8 +18,8 @@ from octavia.api.v1.types import base class ListenerStatisticsResponse(base.BaseType): - """Defines which attributes are to be shown on any response.""" bytes_in = wtypes.wsattr(wtypes.IntegerType()) bytes_out = wtypes.wsattr(wtypes.IntegerType()) active_connections = wtypes.wsattr(wtypes.IntegerType()) total_connections = wtypes.wsattr(wtypes.IntegerType()) + request_errors = wtypes.wsattr(wtypes.IntegerType()) diff --git a/octavia/common/data_models.py b/octavia/common/data_models.py index b0fff04bf8..7df214c798 100644 --- a/octavia/common/data_models.py +++ b/octavia/common/data_models.py @@ -135,15 +135,16 @@ class SessionPersistence(BaseDataModel): class ListenerStatistics(BaseDataModel): - def __init__(self, listener_id=None, amphora_id=None, bytes_in=None, - bytes_out=None, active_connections=None, - total_connections=None): + def __init__(self, listener_id=None, amphora_id=None, bytes_in=0, + bytes_out=0, active_connections=0, + total_connections=0, request_errors=0): self.listener_id = listener_id self.amphora_id = amphora_id self.bytes_in = bytes_in self.bytes_out = bytes_out self.active_connections = active_connections self.total_connections = total_connections + self.request_errors = request_errors class HealthMonitor(BaseDataModel): diff --git a/octavia/controller/healthmanager/update_db.py b/octavia/controller/healthmanager/update_db.py index d58627d2fb..b320d0bb72 100644 --- a/octavia/controller/healthmanager/update_db.py +++ b/octavia/controller/healthmanager/update_db.py @@ -232,16 +232,21 @@ class UpdateStatsDb(object): health = { "id": self.FAKE_UUID_1, "listeners": { - "listener-id-1": {"status": constants.OPEN, - 'stats': {'conns': 0, - 'totconns': 0, - 'rx': 0, - 'tx': 0}, - "pools": { - "pool-id-1": {"status": constants.UP, - "members": {"member-id-1": constants.ONLINE} - } - } + "listener-id-1": { + "status": constants.OPEN, + "stats": { + "ereq":0, + "conns": 0, + "totconns": 0, + "rx": 0, + "tx": 0, + }, + "pools": { + "pool-id-1": { + "status": constants.UP, + "members": {"member-id-1": constants.ONLINE} + } + } } } } @@ -256,7 +261,8 @@ class UpdateStatsDb(object): stats = listener.get('stats') stats = {'bytes_in': stats['rx'], 'bytes_out': stats['tx'], 'active_connections': stats['conns'], - 'total_connections': stats['totconns']} + 'total_connections': stats['totconns'], + 'request_errors': stats['ereq']} LOG.debug("Updating listener stats in db and sending event.") LOG.debug("Listener %s / Amphora %s stats: %s", listener_id, amphora_id, stats) diff --git a/octavia/db/migration/alembic_migrations/versions/c11292016060_add_request_errors_for_stats.py b/octavia/db/migration/alembic_migrations/versions/c11292016060_add_request_errors_for_stats.py new file mode 100644 index 0000000000..7c1ed3c67e --- /dev/null +++ b/octavia/db/migration/alembic_migrations/versions/c11292016060_add_request_errors_for_stats.py @@ -0,0 +1,34 @@ +# Copyright 2016 IBM +# +# 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. + +"""adding request error number to listener_statistics table + +Revision ID: c11292016060 +Revises: 9b5473976d6d +Create Date: 2016-08-12 03:37:38.656962 + +""" + +# revision identifiers, used by Alembic. +revision = 'c11292016060' +down_revision = '9b5473976d6d' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('listener_statistics', + sa.Column('request_errors', sa.BigInteger(), + nullable=False, default=0)) diff --git a/octavia/db/models.py b/octavia/db/models.py index 30632c2450..1c03ab25aa 100644 --- a/octavia/db/models.py +++ b/octavia/db/models.py @@ -126,19 +126,17 @@ class ListenerStatistics(base_models.BASE): bytes_out = sa.Column(sa.BigInteger, nullable=False) active_connections = sa.Column(sa.Integer, nullable=False) total_connections = sa.Column(sa.BigInteger, nullable=False) + request_errors = sa.Column(sa.BigInteger, nullable=False) - @staticmethod @validates('bytes_in', 'bytes_out', - 'active_connections', 'total_connections') - def validate_non_negative_int(key, value): + 'active_connections', 'total_connections', + 'request_errors') + def validate_non_negative_int(self, key, value): if value < 0: data = {'key': key, 'value': value} - raise ValueError(data) - # TODO(trevor-vardeman): Repair this functionality after OpenStack - # Common is in - # raise ValueError(_('The %(key)s field can not have ' - # 'negative value. ' - # 'Current value is %(value)d.') % data) + raise ValueError(_('The %(key)s field can not have ' + 'negative value. ' + 'Current value is %(value)d.') % data) return value diff --git a/octavia/tests/functional/api/v1/base.py b/octavia/tests/functional/api/v1/base.py index c7608896dd..7b184bfcfc 100644 --- a/octavia/tests/functional/api/v1/base.py +++ b/octavia/tests/functional/api/v1/base.py @@ -133,7 +133,8 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase): db_ls = self.listener_stats_repo.create( db_api.get_session(), listener_id=listener_id, amphora_id=amphora_id, bytes_in=0, - bytes_out=0, active_connections=0, total_connections=0) + bytes_out=0, active_connections=0, total_connections=0, + request_errors=0) return db_ls.to_dict() def create_amphora(self, amphora_id, loadbalancer_id, **optionals): diff --git a/octavia/tests/functional/api/v1/test_listener_statistics.py b/octavia/tests/functional/api/v1/test_listener_statistics.py index a99939a7fb..18be36c9a3 100644 --- a/octavia/tests/functional/api/v1/test_listener_statistics.py +++ b/octavia/tests/functional/api/v1/test_listener_statistics.py @@ -36,8 +36,15 @@ class TestListenerStatistics(base.BaseAPITest): def test_get(self): ls = self.create_listener_stats(listener_id=self.listener.get('id'), amphora_id=self.amphora.id) - ls.pop('listener_id') - ls.pop('amphora_id') + expected = { + 'listener': { + 'bytes_in': ls['bytes_in'], + 'bytes_out': ls['bytes_out'], + 'active_connections': ls['active_connections'], + 'total_connections': ls['total_connections'], + 'request_errors': ls['request_errors'] + } + } response = self.get(self.ls_path) response_body = response.json - self.assertEqual(ls, response_body) + self.assertEqual(expected, response_body) diff --git a/octavia/tests/functional/db/test_models.py b/octavia/tests/functional/db/test_models.py index b83a60c863..abc4b149ce 100644 --- a/octavia/tests/functional/db/test_models.py +++ b/octavia/tests/functional/db/test_models.py @@ -57,7 +57,8 @@ class ModelTestMixin(object): 'bytes_in': 0, 'bytes_out': 0, 'active_connections': 0, - 'total_connections': 0} + 'total_connections': 0, + 'request_errors': 0} kwargs.update(overrides) return self._insert(session, models.ListenerStatistics, kwargs) @@ -392,6 +393,13 @@ class ListenerStatisticsModelTest(base.OctaviaDBTestBase, ModelTestMixin): self.create_listener_statistics(self.session, self.listener.id, self.amphora.id) + def test_create_with_negative_int(self): + overrides = {'bytes_in': -1} + self.assertRaises(ValueError, + self.create_listener_statistics, + self.session, self.listener.id, + self.amphora.id, **overrides) + def test_update(self): stats = self.create_listener_statistics(self.session, self.listener.id, self.amphora.id) diff --git a/octavia/tests/functional/db/test_repositories.py b/octavia/tests/functional/db/test_repositories.py index 37877f0406..2e150fc4c0 100644 --- a/octavia/tests/functional/db/test_repositories.py +++ b/octavia/tests/functional/db/test_repositories.py @@ -799,7 +799,7 @@ class TestListenerRepositoryTest(BaseRepositoryTest): self.listener_stats_repo.create( self.session, listener_id=listener.id, amphora_id=amphora.id, bytes_in=1, bytes_out=1, - active_connections=1, total_connections=1) + active_connections=1, total_connections=1, request_errors=1) new_listener = self.listener_repo.get(self.session, id=listener.id) self.assertIsNotNone(new_listener) self.assertIsNotNone(self.listener_stats_repo.get( @@ -846,7 +846,7 @@ class TestListenerRepositoryTest(BaseRepositoryTest): self.session, listener_id=listener.id, amphora_id=amphora.id, bytes_in=1, bytes_out=1, - active_connections=1, total_connections=1) + active_connections=1, total_connections=1, request_errors=1) new_listener = self.listener_repo.get(self.session, id=listener.id) self.assertIsNotNone(new_listener) self.assertEqual(pool, new_listener.default_pool) @@ -909,7 +909,7 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest): stats = self.listener_stats_repo.create( self.session, listener_id=listener_id, amphora_id=amphora_id, bytes_in=1, bytes_out=1, - active_connections=1, total_connections=1) + active_connections=1, total_connections=1, request_errors=1) return stats def test_get(self): @@ -928,6 +928,7 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest): self.assertEqual(1, new_stats.bytes_out) self.assertEqual(1, new_stats.active_connections) self.assertEqual(1, new_stats.total_connections) + self.assertEqual(1, new_stats.request_errors) def test_update(self): bytes_in_change = 2 @@ -956,6 +957,7 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest): bytes_out = random.randrange(1000000000) active_conns = random.randrange(1000000000) total_conns = random.randrange(1000000000) + request_errors = random.randrange(1000000000) self.assertIsNone(self.listener_stats_repo.get( self.session, listener_id=self.listener.id)) self.listener_stats_repo.replace(self.session, self.listener.id, @@ -963,7 +965,8 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest): bytes_in=bytes_in, bytes_out=bytes_out, active_connections=active_conns, - total_connections=total_conns) + total_connections=total_conns, + request_errors=request_errors) obj = self.listener_stats_repo.get(self.session, listener_id=self.listener.id) self.assertIsNotNone(obj) @@ -972,18 +975,21 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest): self.assertEqual(bytes_out, obj.bytes_out) self.assertEqual(active_conns, obj.active_connections) self.assertEqual(total_conns, obj.total_connections) + self.assertEqual(request_errors, obj.request_errors) # Test the update path bytes_in_2 = random.randrange(1000000000) bytes_out_2 = random.randrange(1000000000) active_conns_2 = random.randrange(1000000000) total_conns_2 = random.randrange(1000000000) + request_errors_2 = random.randrange(1000000000) self.listener_stats_repo.replace(self.session, self.listener.id, self.amphora.id, bytes_in=bytes_in_2, bytes_out=bytes_out_2, active_connections=active_conns_2, - total_connections=total_conns_2) + total_connections=total_conns_2, + request_errors=request_errors_2) obj = self.listener_stats_repo.get(self.session, listener_id=self.listener.id) self.assertIsNotNone(obj) @@ -992,6 +998,7 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest): self.assertEqual(bytes_out_2, obj.bytes_out) self.assertEqual(active_conns_2, obj.active_connections) self.assertEqual(total_conns_2, obj.total_connections) + self.assertEqual(request_errors_2, obj.request_errors) class HealthMonitorRepositoryTest(BaseRepositoryTest): diff --git a/octavia/tests/unit/amphorae/backends/health_daemon/test_health_daemon.py b/octavia/tests/unit/amphorae/backends/health_daemon/test_health_daemon.py index 775dc04a4f..46b09b1ed6 100644 --- a/octavia/tests/unit/amphorae/backends/health_daemon/test_health_daemon.py +++ b/octavia/tests/unit/amphorae/backends/health_daemon/test_health_daemon.py @@ -103,23 +103,32 @@ SAMPLE_STATS = ({'': '', 'status': 'OPEN', 'lastchg': '', 'hrsp_2xx': '0', 'act': '1', 'chkdown': '0', 'svname': 'BACKEND', 'hrsp_3xx': '0'}) -SAMPLE_STATS_MSG = {'listeners': { - LISTENER_ID1: { - 'pools': {'432fc8b3-d446-48d4-bb64-13beb90e22bc': { - 'members': { - '302e33d9-dee1-4de9-98d5-36329a06fb58': - 'DOWN'}, - 'status': 'UP'}}, 'stats': { - 'totconns': 0, 'conns': 0, 'tx': 0, 'rx': 0}, - 'status': 'OPEN'}, - LISTENER_ID2: { - 'pools': {'432fc8b3-d446-48d4-bb64-13beb90e22bc': { - 'members': { - '302e33d9-dee1-4de9-98d5-36329a06fb58': - 'DOWN'}, - 'status': 'UP'}}, 'stats': { - 'totconns': 0, 'conns': 0, 'tx': 0, 'rx': 0}, - 'status': 'OPEN'}}, 'id': None, 'seq': 0} +SAMPLE_STATS_MSG = { + 'listeners': { + LISTENER_ID1: { + 'pools': { + '432fc8b3-d446-48d4-bb64-13beb90e22bc': { + 'members': { + '302e33d9-dee1-4de9-98d5-36329a06fb58': 'DOWN'}, + 'status': 'UP'}}, + 'stats': { + 'totconns': 0, 'conns': 0, + 'tx': 0, 'rx': 0, 'ereq': 0}, + 'status': 'OPEN'}, + LISTENER_ID2: { + 'pools': { + '432fc8b3-d446-48d4-bb64-13beb90e22bc': { + 'members': { + '302e33d9-dee1-4de9-98d5-36329a06fb58': 'DOWN'}, + 'status': 'UP'}}, + 'stats': { + 'totconns': 0, 'conns': 0, + 'tx': 0, 'rx': 0, 'ereq': 0}, + 'status': 'OPEN'} + }, + 'id': None, + 'seq': 0 +} class TestHealthDaemon(base.TestCase): diff --git a/octavia/tests/unit/controller/healthmanager/test_update_db.py b/octavia/tests/unit/controller/healthmanager/test_update_db.py index 295d444a67..51ab7e19ff 100644 --- a/octavia/tests/unit/controller/healthmanager/test_update_db.py +++ b/octavia/tests/unit/controller/healthmanager/test_update_db.py @@ -458,6 +458,7 @@ class TestUpdateStatsDb(base.TestCase): self.bytes_in = random.randrange(1000000000) self.bytes_out = random.randrange(1000000000) + self.request_errors = random.randrange(1000000000) self.active_conns = random.randrange(1000000000) self.total_conns = random.randrange(1000000000) self.loadbalancer_id = uuidutils.generate_uuid() @@ -469,18 +470,24 @@ class TestUpdateStatsDb(base.TestCase): health = { "id": self.loadbalancer_id, "listeners": { - self.listener_id: {"status": constants.OPEN, - "stats": {"conns": self.active_conns, - "totconns": self.total_conns, - "rx": self.bytes_in, - "tx": self.bytes_out}, - "pools": {"pool-id-1": - {"status": constants.UP, - "members": - {"member-id-1": constants.ONLINE} - } - } - }}} + self.listener_id: { + "status": constants.OPEN, + "stats": { + "ereq": self.request_errors, + "conns": self.active_conns, + "totconns": self.total_conns, + "rx": self.bytes_in, + "tx": self.bytes_out, + }, + "pools": { + "pool-id-1": { + "status": constants.UP, + "members": {"member-id-1": constants.ONLINE} + } + } + } + } + } session.return_value = 'blah' @@ -490,7 +497,8 @@ class TestUpdateStatsDb(base.TestCase): 'blah', self.listener_id, self.loadbalancer_id, bytes_in=self.bytes_in, bytes_out=self.bytes_out, active_connections=self.active_conns, - total_connections=self.total_conns) + total_connections=self.total_conns, + request_errors=self.request_errors) self.event_client.cast.assert_called_once_with( {}, 'update_info', container={ 'info_type': 'listener_stats', @@ -499,4 +507,5 @@ class TestUpdateStatsDb(base.TestCase): 'bytes_in': self.bytes_in, 'total_connections': self.total_conns, 'active_connections': self.active_conns, - 'bytes_out': self.bytes_out}}) + 'bytes_out': self.bytes_out, + 'request_errors': self.request_errors}})