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**:: **Response Example**::
{ {
'bytes_in': 1000, 'listener': {
'bytes_out': 1000, 'bytes_in': 1000,
'active_connections': 1, 'bytes_out': 1000,
'total_connections': 1 'active_connections': 1,
'total_connections': 1,
'request_errors': 0
}
} }
Create Listener Create Listener

View File

@ -83,9 +83,14 @@ def build_stats_message():
SEQ += 1 SEQ += 1
stat_sock_files = list_sock_stat_files() stat_sock_files = list_sock_stat_files()
for listener_id, stat_sock_file in six.iteritems(stat_sock_files): for listener_id, stat_sock_file in six.iteritems(stat_sock_files):
listener_dict = {'pools': {}, 'status': 'DOWN', listener_dict = {'pools': {},
'stats': {'tx': 0, 'rx': 0, 'status': 'DOWN',
'conns': 0, 'totconns': 0}} 'stats': {
'tx': 0,
'rx': 0,
'conns': 0,
'totconns': 0,
'ereq': 0}}
msg['listeners'][listener_id] = listener_dict msg['listeners'][listener_id] = listener_dict
if util.is_listener_running(listener_id): if util.is_listener_running(listener_id):
(stats, pool_status) = get_stats(stat_sock_file) (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']['rx'] = int(row['bin'])
listener_dict['stats']['conns'] = int(row['scur']) listener_dict['stats']['conns'] = int(row['scur'])
listener_dict['stats']['totconns'] = int(row['stot']) listener_dict['stats']['totconns'] = int(row['stot'])
listener_dict['stats']['ereq'] = int(row['ereq'])
listener_dict['status'] = row['status'] listener_dict['status'] = row['status']
for oid, pool in six.iteritems(pool_status): for oid, pool in six.iteritems(pool_status):
if oid != listener_id: if oid != listener_id:

View File

@ -15,10 +15,12 @@
import logging import logging
import pecan import pecan
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan from wsmeext import pecan as wsme_pecan
from octavia.api.v1.controllers import base from octavia.api.v1.controllers import base
from octavia.api.v1.types import listener_statistics as ls_types 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 data_models
from octavia.common import exceptions from octavia.common import exceptions
from octavia.i18n import _LI from octavia.i18n import _LI
@ -45,7 +47,7 @@ class ListenerStatisticsController(base.BaseController):
id=self.listener_id) id=self.listener_id)
return db_ls return db_ls
@wsme_pecan.wsexpose(ls_types.ListenerStatisticsResponse) @wsme_pecan.wsexpose({wtypes.text: ls_types.ListenerStatisticsResponse})
def get_all(self): def get_all(self):
"""Gets a single listener's statistics details.""" """Gets a single listener's statistics details."""
# NOTE(sbalukoff): since a listener can only have one set of # NOTE(sbalukoff): since a listener can only have one set of
@ -53,5 +55,5 @@ class ListenerStatisticsController(base.BaseController):
# the single set of stats # the single set of stats
context = pecan.request.context.get('octavia_context') context = pecan.request.context.get('octavia_context')
db_ls = self._get_db_ls(context.session) db_ls = self._get_db_ls(context.session)
return self._convert_db_to_type(db_ls, return {constants.LISTENER: self._convert_db_to_type(
ls_types.ListenerStatisticsResponse) db_ls, ls_types.ListenerStatisticsResponse)}

View File

@ -18,8 +18,8 @@ from octavia.api.v1.types import base
class ListenerStatisticsResponse(base.BaseType): class ListenerStatisticsResponse(base.BaseType):
"""Defines which attributes are to be shown on any response."""
bytes_in = wtypes.wsattr(wtypes.IntegerType()) bytes_in = wtypes.wsattr(wtypes.IntegerType())
bytes_out = wtypes.wsattr(wtypes.IntegerType()) bytes_out = wtypes.wsattr(wtypes.IntegerType())
active_connections = wtypes.wsattr(wtypes.IntegerType()) active_connections = wtypes.wsattr(wtypes.IntegerType())
total_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): class ListenerStatistics(BaseDataModel):
def __init__(self, listener_id=None, amphora_id=None, bytes_in=None, def __init__(self, listener_id=None, amphora_id=None, bytes_in=0,
bytes_out=None, active_connections=None, bytes_out=0, active_connections=0,
total_connections=None): total_connections=0, request_errors=0):
self.listener_id = listener_id self.listener_id = listener_id
self.amphora_id = amphora_id self.amphora_id = amphora_id
self.bytes_in = bytes_in self.bytes_in = bytes_in
self.bytes_out = bytes_out self.bytes_out = bytes_out
self.active_connections = active_connections self.active_connections = active_connections
self.total_connections = total_connections self.total_connections = total_connections
self.request_errors = request_errors
class HealthMonitor(BaseDataModel): class HealthMonitor(BaseDataModel):

View File

@ -232,16 +232,21 @@ class UpdateStatsDb(object):
health = { health = {
"id": self.FAKE_UUID_1, "id": self.FAKE_UUID_1,
"listeners": { "listeners": {
"listener-id-1": {"status": constants.OPEN, "listener-id-1": {
'stats': {'conns': 0, "status": constants.OPEN,
'totconns': 0, "stats": {
'rx': 0, "ereq":0,
'tx': 0}, "conns": 0,
"pools": { "totconns": 0,
"pool-id-1": {"status": constants.UP, "rx": 0,
"members": {"member-id-1": constants.ONLINE} "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 = listener.get('stats')
stats = {'bytes_in': stats['rx'], 'bytes_out': stats['tx'], stats = {'bytes_in': stats['rx'], 'bytes_out': stats['tx'],
'active_connections': stats['conns'], '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("Updating listener stats in db and sending event.")
LOG.debug("Listener %s / Amphora %s stats: %s", LOG.debug("Listener %s / Amphora %s stats: %s",
listener_id, amphora_id, stats) 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) bytes_out = sa.Column(sa.BigInteger, nullable=False)
active_connections = sa.Column(sa.Integer, nullable=False) active_connections = sa.Column(sa.Integer, nullable=False)
total_connections = sa.Column(sa.BigInteger, nullable=False) total_connections = sa.Column(sa.BigInteger, nullable=False)
request_errors = sa.Column(sa.BigInteger, nullable=False)
@staticmethod
@validates('bytes_in', 'bytes_out', @validates('bytes_in', 'bytes_out',
'active_connections', 'total_connections') 'active_connections', 'total_connections',
def validate_non_negative_int(key, value): 'request_errors')
def validate_non_negative_int(self, key, value):
if value < 0: if value < 0:
data = {'key': key, 'value': value} data = {'key': key, 'value': value}
raise ValueError(data) raise ValueError(_('The %(key)s field can not have '
# TODO(trevor-vardeman): Repair this functionality after OpenStack 'negative value. '
# Common is in '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 return value

View File

@ -133,7 +133,8 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase):
db_ls = self.listener_stats_repo.create( db_ls = self.listener_stats_repo.create(
db_api.get_session(), listener_id=listener_id, db_api.get_session(), listener_id=listener_id,
amphora_id=amphora_id, bytes_in=0, 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() return db_ls.to_dict()
def create_amphora(self, amphora_id, loadbalancer_id, **optionals): def create_amphora(self, amphora_id, loadbalancer_id, **optionals):

View File

@ -36,8 +36,15 @@ class TestListenerStatistics(base.BaseAPITest):
def test_get(self): def test_get(self):
ls = self.create_listener_stats(listener_id=self.listener.get('id'), ls = self.create_listener_stats(listener_id=self.listener.get('id'),
amphora_id=self.amphora.id) amphora_id=self.amphora.id)
ls.pop('listener_id') expected = {
ls.pop('amphora_id') '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 = self.get(self.ls_path)
response_body = response.json 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_in': 0,
'bytes_out': 0, 'bytes_out': 0,
'active_connections': 0, 'active_connections': 0,
'total_connections': 0} 'total_connections': 0,
'request_errors': 0}
kwargs.update(overrides) kwargs.update(overrides)
return self._insert(session, models.ListenerStatistics, kwargs) 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.create_listener_statistics(self.session, self.listener.id,
self.amphora.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): def test_update(self):
stats = self.create_listener_statistics(self.session, self.listener.id, stats = self.create_listener_statistics(self.session, self.listener.id,
self.amphora.id) self.amphora.id)

View File

@ -799,7 +799,7 @@ class TestListenerRepositoryTest(BaseRepositoryTest):
self.listener_stats_repo.create( self.listener_stats_repo.create(
self.session, listener_id=listener.id, amphora_id=amphora.id, self.session, listener_id=listener.id, amphora_id=amphora.id,
bytes_in=1, bytes_out=1, 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) new_listener = self.listener_repo.get(self.session, id=listener.id)
self.assertIsNotNone(new_listener) self.assertIsNotNone(new_listener)
self.assertIsNotNone(self.listener_stats_repo.get( self.assertIsNotNone(self.listener_stats_repo.get(
@ -846,7 +846,7 @@ class TestListenerRepositoryTest(BaseRepositoryTest):
self.session, listener_id=listener.id, self.session, listener_id=listener.id,
amphora_id=amphora.id, amphora_id=amphora.id,
bytes_in=1, bytes_out=1, 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) new_listener = self.listener_repo.get(self.session, id=listener.id)
self.assertIsNotNone(new_listener) self.assertIsNotNone(new_listener)
self.assertEqual(pool, new_listener.default_pool) self.assertEqual(pool, new_listener.default_pool)
@ -909,7 +909,7 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest):
stats = self.listener_stats_repo.create( stats = self.listener_stats_repo.create(
self.session, listener_id=listener_id, amphora_id=amphora_id, self.session, listener_id=listener_id, amphora_id=amphora_id,
bytes_in=1, bytes_out=1, bytes_in=1, bytes_out=1,
active_connections=1, total_connections=1) active_connections=1, total_connections=1, request_errors=1)
return stats return stats
def test_get(self): def test_get(self):
@ -928,6 +928,7 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest):
self.assertEqual(1, new_stats.bytes_out) self.assertEqual(1, new_stats.bytes_out)
self.assertEqual(1, new_stats.active_connections) self.assertEqual(1, new_stats.active_connections)
self.assertEqual(1, new_stats.total_connections) self.assertEqual(1, new_stats.total_connections)
self.assertEqual(1, new_stats.request_errors)
def test_update(self): def test_update(self):
bytes_in_change = 2 bytes_in_change = 2
@ -956,6 +957,7 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest):
bytes_out = random.randrange(1000000000) bytes_out = random.randrange(1000000000)
active_conns = random.randrange(1000000000) active_conns = random.randrange(1000000000)
total_conns = random.randrange(1000000000) total_conns = random.randrange(1000000000)
request_errors = random.randrange(1000000000)
self.assertIsNone(self.listener_stats_repo.get( self.assertIsNone(self.listener_stats_repo.get(
self.session, listener_id=self.listener.id)) self.session, listener_id=self.listener.id))
self.listener_stats_repo.replace(self.session, 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_in=bytes_in,
bytes_out=bytes_out, bytes_out=bytes_out,
active_connections=active_conns, active_connections=active_conns,
total_connections=total_conns) total_connections=total_conns,
request_errors=request_errors)
obj = self.listener_stats_repo.get(self.session, obj = self.listener_stats_repo.get(self.session,
listener_id=self.listener.id) listener_id=self.listener.id)
self.assertIsNotNone(obj) self.assertIsNotNone(obj)
@ -972,18 +975,21 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest):
self.assertEqual(bytes_out, obj.bytes_out) self.assertEqual(bytes_out, obj.bytes_out)
self.assertEqual(active_conns, obj.active_connections) self.assertEqual(active_conns, obj.active_connections)
self.assertEqual(total_conns, obj.total_connections) self.assertEqual(total_conns, obj.total_connections)
self.assertEqual(request_errors, obj.request_errors)
# Test the update path # Test the update path
bytes_in_2 = random.randrange(1000000000) bytes_in_2 = random.randrange(1000000000)
bytes_out_2 = random.randrange(1000000000) bytes_out_2 = random.randrange(1000000000)
active_conns_2 = random.randrange(1000000000) active_conns_2 = random.randrange(1000000000)
total_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.listener_stats_repo.replace(self.session, self.listener.id,
self.amphora.id, self.amphora.id,
bytes_in=bytes_in_2, bytes_in=bytes_in_2,
bytes_out=bytes_out_2, bytes_out=bytes_out_2,
active_connections=active_conns_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, obj = self.listener_stats_repo.get(self.session,
listener_id=self.listener.id) listener_id=self.listener.id)
self.assertIsNotNone(obj) self.assertIsNotNone(obj)
@ -992,6 +998,7 @@ class ListenerStatisticsRepositoryTest(BaseRepositoryTest):
self.assertEqual(bytes_out_2, obj.bytes_out) self.assertEqual(bytes_out_2, obj.bytes_out)
self.assertEqual(active_conns_2, obj.active_connections) self.assertEqual(active_conns_2, obj.active_connections)
self.assertEqual(total_conns_2, obj.total_connections) self.assertEqual(total_conns_2, obj.total_connections)
self.assertEqual(request_errors_2, obj.request_errors)
class HealthMonitorRepositoryTest(BaseRepositoryTest): class HealthMonitorRepositoryTest(BaseRepositoryTest):

View File

@ -103,23 +103,32 @@ SAMPLE_STATS = ({'': '', 'status': 'OPEN', 'lastchg': '',
'hrsp_2xx': '0', 'act': '1', 'chkdown': '0', 'hrsp_2xx': '0', 'act': '1', 'chkdown': '0',
'svname': 'BACKEND', 'hrsp_3xx': '0'}) 'svname': 'BACKEND', 'hrsp_3xx': '0'})
SAMPLE_STATS_MSG = {'listeners': { SAMPLE_STATS_MSG = {
LISTENER_ID1: { 'listeners': {
'pools': {'432fc8b3-d446-48d4-bb64-13beb90e22bc': { LISTENER_ID1: {
'members': { 'pools': {
'302e33d9-dee1-4de9-98d5-36329a06fb58': '432fc8b3-d446-48d4-bb64-13beb90e22bc': {
'DOWN'}, 'members': {
'status': 'UP'}}, 'stats': { '302e33d9-dee1-4de9-98d5-36329a06fb58': 'DOWN'},
'totconns': 0, 'conns': 0, 'tx': 0, 'rx': 0}, 'status': 'UP'}},
'status': 'OPEN'}, 'stats': {
LISTENER_ID2: { 'totconns': 0, 'conns': 0,
'pools': {'432fc8b3-d446-48d4-bb64-13beb90e22bc': { 'tx': 0, 'rx': 0, 'ereq': 0},
'members': { 'status': 'OPEN'},
'302e33d9-dee1-4de9-98d5-36329a06fb58': LISTENER_ID2: {
'DOWN'}, 'pools': {
'status': 'UP'}}, 'stats': { '432fc8b3-d446-48d4-bb64-13beb90e22bc': {
'totconns': 0, 'conns': 0, 'tx': 0, 'rx': 0}, 'members': {
'status': 'OPEN'}}, 'id': None, 'seq': 0} '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): class TestHealthDaemon(base.TestCase):

View File

@ -458,6 +458,7 @@ class TestUpdateStatsDb(base.TestCase):
self.bytes_in = random.randrange(1000000000) self.bytes_in = random.randrange(1000000000)
self.bytes_out = random.randrange(1000000000) self.bytes_out = random.randrange(1000000000)
self.request_errors = random.randrange(1000000000)
self.active_conns = random.randrange(1000000000) self.active_conns = random.randrange(1000000000)
self.total_conns = random.randrange(1000000000) self.total_conns = random.randrange(1000000000)
self.loadbalancer_id = uuidutils.generate_uuid() self.loadbalancer_id = uuidutils.generate_uuid()
@ -469,18 +470,24 @@ class TestUpdateStatsDb(base.TestCase):
health = { health = {
"id": self.loadbalancer_id, "id": self.loadbalancer_id,
"listeners": { "listeners": {
self.listener_id: {"status": constants.OPEN, self.listener_id: {
"stats": {"conns": self.active_conns, "status": constants.OPEN,
"totconns": self.total_conns, "stats": {
"rx": self.bytes_in, "ereq": self.request_errors,
"tx": self.bytes_out}, "conns": self.active_conns,
"pools": {"pool-id-1": "totconns": self.total_conns,
{"status": constants.UP, "rx": self.bytes_in,
"members": "tx": self.bytes_out,
{"member-id-1": constants.ONLINE} },
} "pools": {
} "pool-id-1": {
}}} "status": constants.UP,
"members": {"member-id-1": constants.ONLINE}
}
}
}
}
}
session.return_value = 'blah' session.return_value = 'blah'
@ -490,7 +497,8 @@ class TestUpdateStatsDb(base.TestCase):
'blah', self.listener_id, self.loadbalancer_id, 'blah', self.listener_id, self.loadbalancer_id,
bytes_in=self.bytes_in, bytes_out=self.bytes_out, bytes_in=self.bytes_in, bytes_out=self.bytes_out,
active_connections=self.active_conns, 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( self.event_client.cast.assert_called_once_with(
{}, 'update_info', container={ {}, 'update_info', container={
'info_type': 'listener_stats', 'info_type': 'listener_stats',
@ -499,4 +507,5 @@ class TestUpdateStatsDb(base.TestCase):
'bytes_in': self.bytes_in, 'bytes_in': self.bytes_in,
'total_connections': self.total_conns, 'total_connections': self.total_conns,
'active_connections': self.active_conns, 'active_connections': self.active_conns,
'bytes_out': self.bytes_out}}) 'bytes_out': self.bytes_out,
'request_errors': self.request_errors}})