From 79d1da08cd3ce967e662f5795314634bf7118a6a Mon Sep 17 00:00:00 2001 From: Luka Peschke Date: Wed, 27 Mar 2019 11:30:30 +0100 Subject: [PATCH] Update the predictive pricing This updates the predictive pricing of the horizon dashboard. Work items: * The "flavor" field has been changed to "flavor_name" in order to match gnocchi. * It is now possible to specify the hashmap service to use for predictive pricing through the "CLOUDKITTY_QUOTATION_SERVICE". * The hashmap service used for quotation does now default to "instance" instead of "compute". Change-Id: Ice42fc1687ade87c2a4690e3e52782ecbf7f0ee3 --- .../dashboards/project/rating/views.py | 20 +++-- .../cloudkitty/js/cloudkitty.controller.js | 4 +- .../static/cloudkitty/js/pricing.js | 2 +- .../tests/test_predictive_pricing.py | 80 +++++++++++++++++++ ...e-predictive-pricing-dc97030d8898af0d.yaml | 6 ++ 5 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 cloudkittydashboard/tests/test_predictive_pricing.py create mode 100644 releasenotes/notes/update-predictive-pricing-dc97030d8898af0d.yaml diff --git a/cloudkittydashboard/dashboards/project/rating/views.py b/cloudkittydashboard/dashboards/project/rating/views.py index ac4ca28..4701821 100644 --- a/cloudkittydashboard/dashboards/project/rating/views.py +++ b/cloudkittydashboard/dashboards/project/rating/views.py @@ -11,10 +11,9 @@ # 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 decimal import json +from django.conf import settings from django import http from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -45,13 +44,24 @@ class IndexView(tables.DataTableView): def quote(request): - pricing = "0" + pricing = 0.0 if request.is_ajax(): if request.method == 'POST': json_data = json.loads(request.body) + + def __update_quotation_data(element, service): + if isinstance(element, dict): + element['service'] = service + else: + for elem in element: + __update_quotation_data(elem, service) + try: - pricing = decimal.Decimal(api.cloudkittyclient(request) - .rating.get_quotation(json_data)) + service = getattr( + settings, 'CLOUDKITTY_QUOTATION_SERVICE', 'instance') + __update_quotation_data(json_data, service) + pricing = float(api.cloudkittyclient(request) + .rating.get_quotation(res_data=json_data)) except Exception: exceptions.handle(request, _('Unable to retrieve price.')) diff --git a/cloudkittydashboard/static/cloudkitty/js/cloudkitty.controller.js b/cloudkittydashboard/static/cloudkitty/js/cloudkitty.controller.js index aefea5b..fb71ed0 100644 --- a/cloudkittydashboard/static/cloudkitty/js/cloudkitty.controller.js +++ b/cloudkittydashboard/static/cloudkitty/js/cloudkitty.controller.js @@ -21,7 +21,7 @@ var disk_total = $scope.model.newInstanceSpec.flavor.ephemeral + $scope.model.newInstanceSpec.flavor.disk; var desc_form = { - 'flavor': $scope.model.newInstanceSpec.flavor.name, + 'flavor_name': $scope.model.newInstanceSpec.flavor.name, 'flavor_id': $scope.model.newInstanceSpec.flavor.id, 'vcpus': $scope.model.newInstanceSpec.flavor.vcpus, 'disk': $scope.model.newInstanceSpec.flavor.disk, @@ -34,7 +34,7 @@ 'image_id': $scope.model.newInstanceSpec.source[0].id, } - var form_data = [{"service": "compute", "desc": desc_form, "volume": $scope.model.newInstanceSpec.instance_count}]; + var form_data = [{"desc": desc_form, "volume": $scope.model.newInstanceSpec.instance_count}]; $http.post($window.WEBROOT + 'project/rating/quote', form_data).then(function(res, status) { $scope.price = res.data; diff --git a/cloudkittydashboard/static/cloudkitty/js/pricing.js b/cloudkittydashboard/static/cloudkitty/js/pricing.js index 8fe6280..01972bd 100644 --- a/cloudkittydashboard/static/cloudkitty/js/pricing.js +++ b/cloudkittydashboard/static/cloudkitty/js/pricing.js @@ -78,7 +78,7 @@ pricing = { if (_image != undefined) { desc_form['image_id'] = _image.id } - var form_data = [{"service": "compute", "desc": desc_form, "volume": instance_count}]; + var form_data = [{"desc": desc_form, "volume": instance_count}]; // send the JSON by a POST request var url_data = [ diff --git a/cloudkittydashboard/tests/test_predictive_pricing.py b/cloudkittydashboard/tests/test_predictive_pricing.py new file mode 100644 index 0000000..a5dc317 --- /dev/null +++ b/cloudkittydashboard/tests/test_predictive_pricing.py @@ -0,0 +1,80 @@ +# Copyright 2019 Objectif Libre +# +# 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 +import os + +from django import http +import mock + +from cloudkittydashboard.tests import base + + +class PredictivePricingTest(base.TestCase): + + def setUp(self): + super(PredictivePricingTest, self).setUp() + + os.environ['DJANGO_SETTINGS_MODULE'] = 'openstack_dashboard.settings' + import horizon.tables # noqa + with mock.patch('horizon.tables'): + from cloudkittydashboard.dashboards.project.rating.views \ + import quote + os.environ.pop('DJANGO_SETTINGS_MODULE') + self.quote = quote + + def _test_quote_request_not_ajax_post(self, arg): + request = mock.MagicMock() + if arg == 'ajax': + request.is_ajax.return_value = False + elif arg == 'method': + request.method == 'POST' + resp = self.quote(request) + self.assertIsInstance(resp, http.HttpResponse) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.content.decode(), '0.0') + + def test_quote_request_not_ajax(self): + self._test_quote_request_not_ajax_post('ajax') + + def test_quote_request_wrong_method(self): + self._test_quote_request_not_ajax_post('method') + + @mock.patch('cloudkittydashboard.dashboards.project.rating.views.api') + def test_quote_does_update_request_dict(self, api_mock): + body = [{'service': 'nope'}, {'other_key': None}] + expected_body = [{u'service': 'test_service'}, + {u'other_key': None, 'service': 'test_service'}] + + request = mock.MagicMock() + request.is_ajax.return_value = True + request.method = 'POST' + request.body = json.dumps(body) + + client_mock = mock.MagicMock() + client_mock.rating.get_quotation.return_value = 42.0 + api_mock.cloudkittyclient.return_value = client_mock + + settings = mock.MagicMock() + settings.CLOUDKITTY_QUOTATION_SERVICE = 'test_service' + with mock.patch( + 'cloudkittydashboard.dashboards.project.rating.views.settings', + settings): + resp = self.quote(request) + + api_mock.cloudkittyclient.assert_called_with(request) + client_mock.rating.get_quotation.assert_called_with( + res_data=expected_body) + self.assertIsInstance(resp, http.HttpResponse) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.content.decode(), '42.0') diff --git a/releasenotes/notes/update-predictive-pricing-dc97030d8898af0d.yaml b/releasenotes/notes/update-predictive-pricing-dc97030d8898af0d.yaml new file mode 100644 index 0000000..48d6da7 --- /dev/null +++ b/releasenotes/notes/update-predictive-pricing-dc97030d8898af0d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The predictive pricing has been updated. It is now possible to specify the + HashMap service to use for predictive pricing in Horizon's configuration + file through the ``CLOUDKITTY_QUOTATION_SERVICE`` option.