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:
parent
47220c635a
commit
5dbff28062
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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}})
|
||||
|
|
Loading…
Reference in New Issue