Merge "Improve get_health API"
This commit is contained in:
commit
c4b9a6da40
@ -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())
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
87
distil/tests/unit/service/api/v2/test_health.py
Normal file
87
distil/tests/unit/service/api/v2/test_health.py
Normal 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'))
|
@ -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",
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user