diff --git a/distil/api/v2.py b/distil/api/v2.py index a732a86..1a2b24c 100644 --- a/distil/api/v2.py +++ b/distil/api/v2.py @@ -22,9 +22,9 @@ from distil.api import acl from distil.common import api from distil.common import constants from distil.common import openstack -from distil.service.api.v2 import costs from distil.service.api.v2 import health from distil.service.api.v2 import invoices +from distil.service.api.v2 import measurements from distil.service.api.v2 import products from distil.service.api.v2 import quotations @@ -91,7 +91,7 @@ def measurements_get(): params = _get_request_args() return api.render( - measurements=costs.get_usage( + measurements=measurements.get_measurements( params['project_id'], params['start'], params['end'] ) ) diff --git a/distil/service/api/v2/invoices.py b/distil/service/api/v2/invoices.py index c60cec8..be5c4a7 100644 --- a/distil/service/api/v2/invoices.py +++ b/distil/service/api/v2/invoices.py @@ -16,15 +16,15 @@ from oslo_config import cfg from oslo_log import log as logging +from distil.common import general from distil.erp import utils as erp_utils -from distil.service.api.v2 import utils LOG = logging.getLogger(__name__) CONF = cfg.CONF def get_invoices(project_id, start, end, detailed=False): - project, start, end = utils.convert_project_and_range( + project, start, end = general.convert_project_and_range( project_id, start, end) LOG.info( diff --git a/distil/service/api/v2/measurements.py b/distil/service/api/v2/measurements.py new file mode 100644 index 0000000..61b373c --- /dev/null +++ b/distil/service/api/v2/measurements.py @@ -0,0 +1,63 @@ +# Copyright (c) 2016 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. + +import json + +from oslo_log import log as logging + +from distil.common import general +from distil.db import api as db_api + +LOG = logging.getLogger(__name__) + + +def _build_project_dict(project, usage): + """Builds a dict structure for a given project.""" + + project_dict = {'project_name': project.name, 'project_id': project.id} + + all_resource_ids = [entry.get('resource_id') for entry in usage] + res_list = db_api.resource_get_by_ids(project.id, all_resource_ids) + project_dict['resources'] = {row.id: json.loads(row.info) + for row in res_list} + + for entry in usage: + service = {'name': entry.get('service'), + 'volume': entry.get('volume'), + 'unit': entry.get('unit')} + + resource = project_dict['resources'][entry.get('resource_id')] + service_list = resource.setdefault('services', []) + service_list.append(service) + + return project_dict + + +def get_measurements(project_id, start, end): + valid_project, start, end = general.convert_project_and_range( + project_id, start, end) + + LOG.debug("Get measurements for %s in range: %s - %s" % + (valid_project.id, start, end)) + + usage = db_api.usage_get(valid_project.id, start, end) + + project_dict = _build_project_dict(valid_project, usage) + + # add range: + project_dict['start'] = str(start) + project_dict['end'] = str(end) + + return project_dict diff --git a/distil/tests/unit/service/api/__init__.py b/distil/tests/unit/service/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/distil/tests/unit/service/api/v2/__init__.py b/distil/tests/unit/service/api/v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/distil/tests/unit/service/api/v2/test_invoices.py b/distil/tests/unit/service/api/v2/test_invoices.py new file mode 100644 index 0000000..a5f4f64 --- /dev/null +++ b/distil/tests/unit/service/api/v2/test_invoices.py @@ -0,0 +1,45 @@ +# 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 + +import mock + +from distil.service.api.v2 import invoices +from distil.tests.unit import base + + +class InvoicesTest(base.DistilWithDbTestCase): + @mock.patch('distil.common.general.convert_project_and_range') + @mock.patch('stevedore.driver.DriverManager') + def test_get_invoices(self, mock_driver, mock_convert): + class Project(object): + def __init__(self, id, name): + self.id = id + self.name = name + + start = datetime.utcnow() + end = datetime.utcnow() + mock_convert.return_value = ( + Project('123', 'fake_project'), start, end + ) + + driver_manager = mock_driver.return_value + driver = driver_manager.driver + + invoices.get_invoices('123', str(start), str(end)) + + driver.get_invoices.assert_called_once_with( + start, end, '123', detailed=False + ) diff --git a/distil/tests/unit/service/api/v2/test_measurements.py b/distil/tests/unit/service/api/v2/test_measurements.py new file mode 100644 index 0000000..0cab70c --- /dev/null +++ b/distil/tests/unit/service/api/v2/test_measurements.py @@ -0,0 +1,91 @@ +# 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 +import json + +import mock + +from distil.service.api.v2 import measurements +from distil.tests.unit import base + + +class MeasurementsTest(base.DistilWithDbTestCase): + @mock.patch('distil.common.general.convert_project_and_range') + @mock.patch('distil.db.api.usage_get') + @mock.patch('distil.db.api.resource_get_by_ids') + def test_get_measurements(self, mock_get_resources, mock_db_usage_get, + mock_convert): + class Project(object): + def __init__(self, id, name): + self.id = id + self.name = name + + start = datetime.utcnow() + end = datetime.utcnow() + mock_convert.return_value = ( + Project('123', 'fake_name'), start, end + ) + + mock_db_usage_get.return_value = [ + { + 'resource_id': '111', + 'service': 'srv1', + 'volume': 10, + 'unit': 'byte', + }, + { + 'resource_id': '222', + 'service': 'srv2', + 'volume': 20, + 'unit': 'byte', + } + ] + + class Resource(object): + def __init__(self, id, info): + self.id = id + self.info = info + + res1 = Resource('111', json.dumps({'name': 'resource1'})) + res2 = Resource('222', json.dumps({'name': 'resource2'})) + mock_get_resources.return_value = [res1, res2] + + project_measures = measurements.get_measurements( + '123', str(start), str(end) + ) + + self.assertEqual( + { + 'start': str(start), + 'end': str(end), + 'project_name': 'fake_name', + 'project_id': '123', + 'resources': { + '111': { + 'name': 'resource1', + 'services': [ + {'name': 'srv1', 'volume': 10, 'unit': 'byte'} + ] + }, + '222': { + 'name': 'resource2', + 'services': [ + {'name': 'srv2', 'volume': 20, 'unit': 'byte'} + ] + } + } + }, + project_measures + ) diff --git a/requirements.txt b/requirements.txt index e1e40ea..de5d53f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ # from above list. And make sure the versions are sync with OpenStack global # requirements. -Babel==1.3 +Babel>=2.3.4,!=2.4.0 # BSD Flask<1.0,>=0.10 # BSD pbr>=1.6 # Apache-2.0 six>=1.9.0 # MIT