Update listener stats

1. Add request error count
2. Add root element 'listener' in the API response body

Change-Id: I8beb918c176ed848affa264cb036763240d07dcd
Implements: blueprint stats-support
This commit is contained in:
chen-li 2016-08-12 03:46:43 -05:00
parent 47220c635a
commit 5dbff28062
14 changed files with 166 additions and 75 deletions

View File

@ -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

View File

@ -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:

View File

@ -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)}

View File

@ -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())

View File

@ -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):

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -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}})