Skip to content
This repository was archived by the owner on Jan 24, 2018. It is now read-only.

[WIP] Create metric and billing interface outline #173

Merged
merged 5 commits into from
Mar 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ addons:
- python-simplejson
- python-serial
- python-yaml
- unixodbc-dev

env:
global:
Expand Down
28 changes: 28 additions & 0 deletions clouder_metric/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3

==============
Clouder Metric
==============

This module provides models to ingest and store usage metrics from ELK about
running instances.

Installation
============

* Install module as normal


Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/clouder-community/clouder>`_.

Contributors
------------

* Dave Lasley <dave@laslabs.com>
* Ted Salmon <tsalmon@laslabs.com>
5 changes: 5 additions & 0 deletions clouder_metric/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change all to LGPL


from . import models
23 changes: 23 additions & 0 deletions clouder_metric/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make a symbolic link from openerp.py to manifest.py to ensure the module can be installed on Odoo 9 (see my last commit on master)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so sure about this. What about the changed namespace (from odoo vs from openerp)? I'm pretty sure I have also used some new API decorators that will not work on v9 (api.model_cr_context)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well at the very least the tricks worked on my dev instance, and all other modules now have the symbolic link. Anyway I'll not block the PR for this, so I'm just waiting for your approval.

# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Clouder - Metrics",
"summary": "Provides Usage Metric Interface for Clouder",
"version": "10.0.1.0.0",
"category": "Clouder",
"website": "https://github.com/clouder-community/clouder",
"author": "LasLabs",
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": [
'base_external_dbsource',
"clouder",
"contract_variable_quantity",
"sale",
],
"data": [
"security/ir.model.access.csv",
],
}
7 changes: 7 additions & 0 deletions clouder_metric/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import clouder_metric_type
from . import clouder_metric_interface
from . import clouder_metric_value
70 changes: 70 additions & 0 deletions clouder_metric/models/clouder_metric_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class ClouderMetricInterface(models.Model):
""" It provides a common interface for Clouder Usage metrics.

This object receives all attributes from ``clouder.metric.type``.
"""

_name = 'clouder.metric.interface'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_description key on all new models

_description = 'Clouder Metric Interfaces'
_inherits = {'clouder.metric.type': 'type_id'}

type_id = fields.Many2one(
string='Metric Type',
comodel_name='clouder.metric.type',
required=True,
ondelete='restrict',
)
metric_value_ids = fields.One2many(
string='Metric Values',
comodel_name='clouder.metric.value',
inverse_name='interface_id',
)
metric_model = fields.Selection(
related='type_id.metric_model',
)
metric_ref = fields.Integer(
required=True,
)
source_id = fields.Many2one(
string='Metric Source',
comodel_name='base.external.dbsource',
domain="[('connector', '=', type_id.connector_type)]",
required=True,
)
cron_id = fields.Many2one(
string='Scheduled Task',
comodel_name='ir.cron',
domain="[('model', '=', _name)]",
context="""{
'default_model': _name,
'default_name': '[Clouder Metric] %s' % display_name,
}""",
)
interval_number = fields.Integer(
related='cron_id.interval_number',
)
interval_type = fields.Selection(
related='cron_id.interval_type',
)
query_code = fields.Text()

@property
@api.multi
def metric_id(self):
self.ensure_one()
return self.env[self.metric_model].browse(
self.metric_ref,
)

@api.multi
def name_get(self):
return [
(r.id, '%s - %s' % (r.type_id.name, r.metric_id.id)) for r in self
]
114 changes: 114 additions & 0 deletions clouder_metric/models/clouder_metric_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models
from odoo.tools import safe_eval
from odoo.exceptions import ValidationError, UserError


class ClouderMetricType(models.Model):
""" It provides context for usage metric types """

_name = 'clouder.metric.type'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_description key on all new models

_description = 'Clouder Metric Types'

name = fields.Char()
code = fields.Char()
metric_model = fields.Selection(
selection=lambda s: s._get_metric_models(),
required=True,
help='Clouder entity type that this metric is related to.',
)
uom_id = fields.Many2one(
string='Unit of Measure',
comodel_name='product.uom',
required=True,
)
connector_type = fields.Selection(
selection=lambda s: s.env['base.external.dbsource'].CONNECTORS,
)
metric_code = fields.Text(
required=True,
default=lambda s: s._default_query_code(),
help='Python code to use as query for metric.'
)

@api.model
def _default_query_code(self):
return _("# Python code. \n"
"Use `value = my_value` to specify the final calculated "
" metric value. This is required. \n"
"Optionally use ``uom = product_uom_record`` to change the "
"units that the metric is being measured in. \n"
"You should also add `date_start` and `date_end`, which "
"are `datetime` values to signify the date of occurrence of "
"the metric value in question. \n"
"# You can use the following variables: \n"
"# - self: browse_record of the current ID Category \n"
"# - interface: browse_record of the Metrics Interface. \n"
"# - metric_model: Name of the metric model type. \n")

@api.model
def _get_metric_models(self):
""" Returns a selection of available metric models
Returns:
list: Additional metric models
"""
return [
('clouder.base', 'Base'),
('clouder.service', 'Service'),
]

@api.multi
def _get_query_code_context(self, interface):
""" Returns a query context for use
Args:
interface (clouder.metric.interface): The interface to use
Returns:
dict: Dict with the context for the given iface and model
"""
self.ensure_one()
return {
'interface': interface,
'metric_model': self.metric_model,
'self': self,
}

@api.model
def save_metric_value(self, metric_interfaces):
""" Saves a metric value from the given interface
Args:
metric_interfaces (clouder.metric.interface): The interface to use
Returns:
None
"""
for iface in metric_interfaces:
eval_context = iface.type_id._get_query_code_context(iface)
try:
safe_eval(
iface.query_code,
eval_context,
mode='exec',
nocopy=True,
)
except Exception as e:
raise UserError(_(
'Error while evaluating metrics query:'
'\n %s \n %s' % (iface.name, e),
))
if eval_context.get('value') is None:
raise ValidationError(_(
'Metrics query did not set the `value` variable, which '
'is used to indicate the value that should be saved for '
'the query.',
))
uom = eval_context.get('uom') or iface.uom_id
iface.write({
'metric_value_ids': [(0, 0, {
'value': eval_context['value'],
'date_start': eval_context.get('date_start'),
'date_end': eval_context.get('date_end'),
'uom_id': uom.id,
})],
})
35 changes: 35 additions & 0 deletions clouder_metric/models/clouder_metric_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ClouderMetricValue(models.Model):
""" It provides a record of metric values used in billing. """

_name = 'clouder.metric.value'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_description key on all new models

_description = 'Clouder Metric Values'

interface_id = fields.Many2one(
string='Interface',
comodel_name='clouder.metric.interface',
required=True,
)
value = fields.Float(
required=True,
)
uom_id = fields.Many2one(
string='Unit of Measure',
comodel_name='product.uom',
)
date_start = fields.Datetime(
string='Metric Start',
)
date_end = fields.Datetime(
string='Metric End',
)
date_create = fields.Datetime(
string='Creation Time',
default=lambda s: fields.Datetime.now(),
)
4 changes: 4 additions & 0 deletions clouder_metric/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_clouder_metric_type,access_clouder_metric_type,model_clouder_metric_type,clouder.group_clouder_user,1,0,0,0
access_clouder_metric_interface,access_clouder_metric_interface,model_clouder_metric_interface,clouder.group_clouder_user,1,0,0,0
access_clouder_metric_value,access_clouder_metric_value,model_clouder_metric_value,clouder.group_clouder_user,1,0,0,0
6 changes: 6 additions & 0 deletions clouder_metric/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import test_clouder_metric_interface
from . import test_clouder_metric_type
24 changes: 24 additions & 0 deletions clouder_metric/tests/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo.tests.common import TransactionCase


class TestCommon(TransactionCase):

def setUp(self):
super(TestCommon, self).setUp()
self.metric_type = self.env['clouder.metric.type'].create({
'name': 'Test Metric',
'code': 'TEST',
'metric_model': 'clouder.base',
'uom_id': self.env.ref('product.uom_categ_wtime').id,
})
self.metric_interface = self.env['clouder.metric.interface'].create({
'type_id': self.metric_type.id,
'metric_ref': 7,
'source_id': self.env.ref(
'base_external_dbsource.demo_postgre').id,
'query_code': 'print True',
})
19 changes: 19 additions & 0 deletions clouder_metric/tests/test_clouder_metric_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from .common import TestCommon


class TestClouderMetricInterface(TestCommon):

def test_metric_id(self):
""" It should test to see that at least one metric_id is returned """
self.assertTrue(len(self.metric_interface.metric_id) == 1)

def test_name_get(self):
""" It should return the right name """
exp = [
(self.metric_interface.id, 'Test Metric - 7')
]
self.assertEqual(exp, self.metric_interface.name_get())
37 changes: 37 additions & 0 deletions clouder_metric/tests/test_clouder_metric_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo.exceptions import UserError, ValidationError

from .common import TestCommon


class TestClouderMetricType(TestCommon):

def test_get_metric_models(self):
""" It should have the correct metric model types """
exp = [
('clouder.base', 'Base'),
('clouder.service', 'Service'),
]
self.assertEquals(exp, self.metric_type._get_metric_models())

def test_save_metric_value_usererror(self):
""" It should raise UserError when a bad query is supplied """
with self.assertRaises(UserError):
self.metric_type.save_metric_value(self.metric_interface)

def test_save_metric_value_validationerror(self):
""" It should raise ValidationError when no value is supplied """
self.metric_interface.query_code = 'test = 0'
with self.assertRaises(ValidationError):
self.metric_type.save_metric_value(self.metric_interface)

def test_save_metric_value(self):
""" It should verify that the right metric values are saved """
self.metric_interface.query_code = 'value = 100'
self.metric_type.save_metric_value(self.metric_interface)
self.assertTrue(
self.metric_interface.metric_value_ids.mapped('value') == [100.0]
)
3 changes: 3 additions & 0 deletions oca_dependencies.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
connector
contract
# Pending https://github.com/OCA/server-tools/pull/660
server-tools https://github.com/laslabs/server-tools feature/10.0/base_external_dbsource_elasticsearch-v10
Loading