Merge "Improve get_health API"

This commit is contained in:
Jenkins 2017-06-15 23:59:16 +00:00 committed by Gerrit Code Review
commit c4b9a6da40
7 changed files with 168 additions and 26 deletions

View File

@ -61,6 +61,7 @@ def _get_request_args():
@rest.get('/health')
@acl.enforce("health:get")
def health_get():
return api.render(health=health.get_health())

View File

@ -32,7 +32,7 @@ DEFAULT_OPTIONS = (
help='The listen IP for the Distil API server',
),
cfg.ListOpt('public_api_routes',
default=['/', '/v2/products', '/v2/health'],
default=['/', '/v2/products'],
help='The list of public API routes',
),
cfg.ListOpt('ignore_tenants',

View File

@ -119,8 +119,8 @@ def project_get(project_id):
return IMPL.project_get(project_id)
def project_get_all():
return IMPL.project_get_all()
def project_get_all(**filters):
return IMPL.project_get_all(**filters)
def get_last_collect(project_ids):

View File

@ -110,6 +110,49 @@ def model_query(model, context, session=None, project_only=True):
return query
def apply_filters(query, model, **filters):
"""Apply filter for db query.
Sample of filters:
{
'key1': {'op': 'in', 'value': [1, 2]},
'key2': {'op': 'lt', 'value': 10},
'key3': 'value'
}
"""
filter_dict = {}
for key, criteria in filters.items():
column_attr = getattr(model, key)
if isinstance(criteria, dict):
if criteria['op'] == 'in':
query = query.filter(column_attr.in_(criteria['value']))
elif criteria['op'] == 'nin':
query = query.filter(~column_attr.in_(criteria['value']))
elif criteria['op'] == 'neq':
query = query.filter(column_attr != criteria['value'])
elif criteria['op'] == 'gt':
query = query.filter(column_attr > criteria['value'])
elif criteria['op'] == 'gte':
query = query.filter(column_attr >= criteria['value'])
elif criteria['op'] == 'lt':
query = query.filter(column_attr < criteria['value'])
elif criteria['op'] == 'lte':
query = query.filter(column_attr <= criteria['value'])
elif criteria['op'] == 'eq':
query = query.filter(column_attr == criteria['value'])
elif criteria['op'] == 'like':
like_pattern = '%{0}%'.format(criteria['value'])
query = query.filter(column_attr.like(like_pattern))
else:
filter_dict[key] = criteria
if filter_dict:
query = query.filter_by(**filter_dict)
return query
def _project_get(session, project_id):
return session.query(Tenant).filter_by(id=project_id).first()
@ -139,9 +182,11 @@ def project_add(values, last_collect=None):
return project
def project_get_all():
def project_get_all(**filters):
session = get_session()
query = session.query(Tenant)
query = apply_filters(query, Tenant, **filters)
return query.all()

View File

@ -13,7 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import datetime as dt
from datetime import datetime
from datetime import timedelta
from oslo_config import cfg
from oslo_log import log as logging
@ -25,29 +26,36 @@ CONF = cfg.CONF
def get_health():
health = {}
"""Get health status of distil.
Currently, we only check usage collection to achieve feature parity with
current monitoring requirements.
In future, we could add running status for ERP system, etc.
"""
result = {}
projects_keystone = openstack.get_projects()
project_id_list_keystone = [t['id'] for t in projects_keystone]
projects = db_api.project_get_all()
keystone_projects = [t['id'] for t in projects_keystone]
# NOTE(flwang): Check the last_collected field for each tenant of Distil,
# if the date is old (has not been updated more than 24 hours) and the
# tenant is still active in Keystone, we believe it should be investigated.
failed_collected_count = 0
for p in projects:
delta = (dt.now() - p.last_collected).total_seconds() // 3600
if delta >= 24 and p.id in project_id_list_keystone:
failed_collected_count += 1
threshold = datetime.utcnow() - timedelta(days=1)
# TODO(flwang): The format of health output need to be discussed so that
# we can get a stable format before it's used in monitor.
if failed_collected_count == 0:
health['metrics_collecting'] = {'status': 'OK',
'note': 'All tenants are synced.'}
failed_projects = db_api.project_get_all(
id={'op': 'in', 'value': keystone_projects},
last_collected={'op': 'lte', 'value': threshold}
)
failed_count = len(failed_projects)
if failed_count == 0:
result['usage_collection'] = {
'status': 'OK',
'msg': 'Tenant usage successfully collected and up-to-date.'
}
else:
note = ('Failed to collect metrics for %s projects.' %
failed_collected_count)
health['metrics_collecting'] = {'status': 'FAIL',
'note': note}
result['usage_collection'] = {
'status': 'FAIL',
'msg': 'Failed to collect usage for %s projects.' % failed_count
}
return health
return result

View File

@ -0,0 +1,87 @@
# Copyright (C) 2017 Catalyst IT Ltd
#
# 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.
from datetime import datetime
from datetime import timedelta
import mock
from distil.db.sqlalchemy import api as db_api
from distil.service.api.v2 import health
from distil.tests.unit import base
class HealthTest(base.DistilWithDbTestCase):
@mock.patch('distil.common.openstack.get_projects')
def test_get_health_ok(self, mock_get_projects):
mock_get_projects.return_value = [
{'id': '111', 'name': 'project_1', 'description': ''},
{'id': '222', 'name': 'project_2', 'description': ''},
]
# Insert projects in the database.
project_1_collect = datetime.utcnow() - timedelta(hours=1)
db_api.project_add(
{
'id': '111',
'name': 'project_1',
'description': '',
},
project_1_collect
)
project_2_collect = datetime.utcnow() - timedelta(hours=2)
db_api.project_add(
{
'id': '222',
'name': 'project_2',
'description': '',
},
project_2_collect
)
ret = health.get_health()
self.assertEqual('OK', ret['usage_collection'].get('status'))
@mock.patch('distil.common.openstack.get_projects')
def test_get_health_fail(self, mock_get_projects):
mock_get_projects.return_value = [
{'id': '111', 'name': 'project_1', 'description': ''},
{'id': '222', 'name': 'project_2', 'description': ''},
]
# Insert projects in the database.
project_1_collect = datetime.utcnow() - timedelta(days=2)
db_api.project_add(
{
'id': '111',
'name': 'project_1',
'description': '',
},
project_1_collect
)
project_2_collect = datetime.utcnow() - timedelta(hours=25)
db_api.project_add(
{
'id': '222',
'name': 'project_2',
'description': '',
},
project_2_collect
)
ret = health.get_health()
self.assertEqual('FAIL', ret['usage_collection'].get('status'))
self.assertIn('2', ret['usage_collection'].get('msg'))

View File

@ -6,4 +6,5 @@
"rating:measurements:get": "rule:context_is_admin",
"rating:invoices:get": "rule:context_is_admin",
"rating:quotations:get": "rule:context_is_admin",
"health:get": "rule:context_is_admin",
}