Added EfficacyIndicator object

In this changeset, I created the Watcher object for the efficacy
indicators as well as the API object which will be embedded into the
/action_plans endpoint.

Partially Implements: blueprint efficacy-indicator

Change-Id: Ibbe47613317d51a3829fe9de12540c048e8d7117
This commit is contained in:
Vincent Françoise
2016-05-18 11:42:49 +02:00
parent f665d83657
commit 84b12f8f1e
6 changed files with 428 additions and 3 deletions

View File

@@ -167,4 +167,5 @@ class Controller(rest.RestController):
# the request object to make the links.
return V1.convert()
__all__ = (Controller)
__all__ = ("Controller", )

View File

@@ -63,6 +63,7 @@ import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from watcher._i18n import _
from watcher.api.controllers import base
from watcher.api.controllers import link
from watcher.api.controllers.v1 import collection

View File

@@ -0,0 +1,72 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2016 b<>com
#
# 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.
"""
An efficacy indicator is a single value
that gives an indication on how the :ref:`solution <solution_definition>`
produced by a given :ref:`strategy <strategy_definition>` performed. These
efficacy indicators are specific to a given :ref:`goal <goal_definition>` and
are usually used to compute the :ref:`gobal efficacy <efficacy_definition>` of
the resulting :ref:`action plan <action_plan_definition>`.
In Watcher, these efficacy indicators are specified alongside the goal they
relate to. When a strategy (which always relates to a goal) is executed, it
produces a solution containing the efficacy indicators specified by the
goal. This solution, which has been translated by the :ref:`Watcher Planner
<watcher_planner_definition>` into an action plan, will see its
indicators and global efficacy stored and would now be accessible through the
:ref:`Watcher API <watcher_api_definition>`.
"""
import numbers
from wsme import types as wtypes
from watcher.api.controllers import base
from watcher import objects
class EfficacyIndicator(base.APIBase):
"""API representation of a efficacy indicator.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of an
efficacy indicator.
"""
name = wtypes.wsattr(wtypes.text, mandatory=True)
"""Name of this efficacy indicator"""
description = wtypes.wsattr(wtypes.text, mandatory=False)
"""Description of this efficacy indicator"""
unit = wtypes.wsattr(wtypes.text, mandatory=False)
"""Unit of this efficacy indicator"""
value = wtypes.wsattr(numbers.Number, mandatory=True)
"""Value of this efficacy indicator"""
def __init__(self, **kwargs):
super(EfficacyIndicator, self).__init__()
self.fields = []
fields = list(objects.EfficacyIndicator.fields)
for field in fields:
# Skip fields we do not expose.
if not hasattr(self, field):
continue
self.fields.append(field)
setattr(self, field, kwargs.get(field, wtypes.Unset))

View File

@@ -17,6 +17,7 @@ from watcher.objects import action
from watcher.objects import action_plan
from watcher.objects import audit
from watcher.objects import audit_template
from watcher.objects import efficacy_indicator
from watcher.objects import goal
from watcher.objects import strategy
@@ -26,6 +27,7 @@ Action = action.Action
ActionPlan = action_plan.ActionPlan
Goal = goal.Goal
Strategy = strategy.Strategy
EfficacyIndicator = efficacy_indicator.EfficacyIndicator
__all__ = ("Audit", "AuditTemplate", "Action",
"ActionPlan", "Goal", "Strategy")
__all__ = ("Audit", "AuditTemplate", "Action", "ActionPlan",
"Goal", "Strategy", "EfficacyIndicator")

View File

@@ -0,0 +1,199 @@
# -*- encoding: utf-8 -*-
# Copyright 2013 IBM Corp.
#
# 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 watcher.common import exception
from watcher.common import utils
from watcher.db import api as dbapi
from watcher.objects import base
from watcher.objects import utils as obj_utils
class EfficacyIndicator(base.WatcherObject):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = dbapi.get_instance()
fields = {
'id': int,
'uuid': obj_utils.str_or_none,
'action_plan_id': obj_utils.int_or_none,
'name': obj_utils.str_or_none,
'description': obj_utils.str_or_none,
'unit': obj_utils.str_or_none,
'value': obj_utils.numeric_or_none,
}
@staticmethod
def _from_db_object(efficacy_indicator, db_efficacy_indicator):
"""Converts a database entity to a formal object."""
for field in efficacy_indicator.fields:
efficacy_indicator[field] = db_efficacy_indicator[field]
efficacy_indicator.obj_reset_changes()
return efficacy_indicator
@staticmethod
def _from_db_object_list(db_objects, cls, context):
"""Converts a list of database entities to a list of formal objects."""
return [EfficacyIndicator._from_db_object(cls(context), obj)
for obj in db_objects]
@classmethod
def get(cls, context, efficacy_indicator_id):
"""Find an efficacy indicator object given its ID or UUID
:param efficacy_indicator_id: the ID or UUID of an efficacy indicator.
:returns: a :class:`EfficacyIndicator` object.
"""
if utils.is_int_like(efficacy_indicator_id):
return cls.get_by_id(context, efficacy_indicator_id)
elif utils.is_uuid_like(efficacy_indicator_id):
return cls.get_by_uuid(context, efficacy_indicator_id)
else:
raise exception.InvalidIdentity(identity=efficacy_indicator_id)
@classmethod
def get_by_id(cls, context, efficacy_indicator_id):
"""Find an efficacy indicator given its integer ID
:param efficacy_indicator_id: the id of an efficacy indicator.
:returns: a :class:`EfficacyIndicator` object.
"""
db_efficacy_indicator = cls.dbapi.get_efficacy_indicator_by_id(
context, efficacy_indicator_id)
efficacy_indicator = EfficacyIndicator._from_db_object(
cls(context), db_efficacy_indicator)
return efficacy_indicator
@classmethod
def get_by_uuid(cls, context, uuid):
"""Find an efficacy indicator given its UUID
:param uuid: the uuid of an efficacy indicator.
:param context: Security context
:returns: a :class:`EfficacyIndicator` object.
"""
db_efficacy_indicator = cls.dbapi.get_efficacy_indicator_by_uuid(
context, uuid)
efficacy_indicator = EfficacyIndicator._from_db_object(
cls(context), db_efficacy_indicator)
return efficacy_indicator
@classmethod
def list(cls, context, limit=None, marker=None, filters=None,
sort_key=None, sort_dir=None):
"""Return a list of EfficacyIndicator objects.
:param context: Security context.
:param limit: maximum number of resources to return in a single result.
:param marker: pagination marker for large data sets.
:param filters: Filters to apply. Defaults to None.
:param sort_key: column to sort results by.
:param sort_dir: direction to sort. "asc" or "desc".
:returns: a list of :class:`EfficacyIndicator` object.
"""
db_efficacy_indicators = cls.dbapi.get_efficacy_indicator_list(
context,
limit=limit,
marker=marker,
filters=filters,
sort_key=sort_key,
sort_dir=sort_dir)
return EfficacyIndicator._from_db_object_list(
db_efficacy_indicators, cls, context)
def create(self, context=None):
"""Create a EfficacyIndicator record in the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: EfficacyIndicator(context)
"""
values = self.obj_get_changes()
db_efficacy_indicator = self.dbapi.create_efficacy_indicator(values)
self._from_db_object(self, db_efficacy_indicator)
def destroy(self, context=None):
"""Delete the EfficacyIndicator from the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: EfficacyIndicator(context)
"""
self.dbapi.destroy_efficacy_indicator(self.uuid)
self.obj_reset_changes()
def save(self, context=None):
"""Save updates to this EfficacyIndicator.
Updates will be made column by column based on the result
of self.what_changed().
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: EfficacyIndicator(context)
"""
updates = self.obj_get_changes()
self.dbapi.update_efficacy_indicator(self.uuid, updates)
self.obj_reset_changes()
def refresh(self, context=None):
"""Loads updates for this EfficacyIndicator.
Loads an efficacy indicator with the same uuid from the database and
checks for updated attributes. Updates are applied to the loaded
efficacy indicator column by column, if there are any updates.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: EfficacyIndicator(context)
"""
current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
for field in self.fields:
if (hasattr(self, base.get_attrname(field)) and
self[field] != current[field]):
self[field] = current[field]
def soft_delete(self, context=None):
"""Soft Delete the efficacy indicator from the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Audit(context)
"""
self.dbapi.soft_delete_efficacy_indicator(self.uuid)
self.state = "DELETED"
self.save()

View File

@@ -0,0 +1,150 @@
# Copyright 2015 OpenStack Foundation
# All Rights Reserved.
#
# 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 mock
from testtools.matchers import HasLength
from watcher.common import exception
# from watcher.common import utils as w_utils
from watcher import objects
from watcher.tests.db import base
from watcher.tests.db import utils
class TestEfficacyIndicatorObject(base.DbTestCase):
def setUp(self):
super(TestEfficacyIndicatorObject, self).setUp()
self.fake_efficacy_indicator = utils.get_test_efficacy_indicator()
def test_get_by_id(self):
efficacy_indicator_id = self.fake_efficacy_indicator['id']
with mock.patch.object(self.dbapi, 'get_efficacy_indicator_by_id',
autospec=True) as mock_get_efficacy_indicator:
mock_get_efficacy_indicator.return_value = (
self.fake_efficacy_indicator)
efficacy_indicator = objects.EfficacyIndicator.get(
self.context, efficacy_indicator_id)
mock_get_efficacy_indicator.assert_called_once_with(
self.context, efficacy_indicator_id)
self.assertEqual(self.context, efficacy_indicator._context)
def test_get_by_uuid(self):
uuid = self.fake_efficacy_indicator['uuid']
with mock.patch.object(self.dbapi, 'get_efficacy_indicator_by_uuid',
autospec=True) as mock_get_efficacy_indicator:
mock_get_efficacy_indicator.return_value = (
self.fake_efficacy_indicator)
efficacy_indicator = objects.EfficacyIndicator.get(
self.context, uuid)
mock_get_efficacy_indicator.assert_called_once_with(
self.context, uuid)
self.assertEqual(self.context, efficacy_indicator._context)
def test_get_bad_id_and_uuid(self):
self.assertRaises(
exception.InvalidIdentity,
objects.EfficacyIndicator.get, self.context, 'not-a-uuid')
def test_list(self):
with mock.patch.object(self.dbapi, 'get_efficacy_indicator_list',
autospec=True) as mock_get_list:
mock_get_list.return_value = [self.fake_efficacy_indicator]
efficacy_indicators = objects.EfficacyIndicator.list(self.context)
self.assertEqual(1, mock_get_list.call_count)
self.assertThat(efficacy_indicators, HasLength(1))
self.assertIsInstance(
efficacy_indicators[0], objects.EfficacyIndicator)
self.assertEqual(self.context, efficacy_indicators[0]._context)
def test_create(self):
with mock.patch.object(
self.dbapi, 'create_efficacy_indicator',
autospec=True
) as mock_create_efficacy_indicator:
mock_create_efficacy_indicator.return_value = (
self.fake_efficacy_indicator)
efficacy_indicator = objects.EfficacyIndicator(
self.context, **self.fake_efficacy_indicator)
efficacy_indicator.create()
mock_create_efficacy_indicator.assert_called_once_with(
self.fake_efficacy_indicator)
self.assertEqual(self.context, efficacy_indicator._context)
def test_destroy(self):
uuid = self.fake_efficacy_indicator['uuid']
with mock.patch.object(
self.dbapi, 'get_efficacy_indicator_by_uuid',
autospec=True
) as mock_get_efficacy_indicator:
mock_get_efficacy_indicator.return_value = (
self.fake_efficacy_indicator)
with mock.patch.object(
self.dbapi, 'destroy_efficacy_indicator',
autospec=True
) as mock_destroy_efficacy_indicator:
efficacy_indicator = objects.EfficacyIndicator.get_by_uuid(
self.context, uuid)
efficacy_indicator.destroy()
mock_get_efficacy_indicator.assert_called_once_with(
self.context, uuid)
mock_destroy_efficacy_indicator.assert_called_once_with(uuid)
self.assertEqual(self.context, efficacy_indicator._context)
def test_save(self):
uuid = self.fake_efficacy_indicator['uuid']
with mock.patch.object(
self.dbapi, 'get_efficacy_indicator_by_uuid',
autospec=True
) as mock_get_efficacy_indicator:
mock_get_efficacy_indicator.return_value = (
self.fake_efficacy_indicator)
with mock.patch.object(
self.dbapi, 'update_efficacy_indicator',
autospec=True
) as mock_update_efficacy_indicator:
efficacy_indicator = objects.EfficacyIndicator.get_by_uuid(
self.context, uuid)
efficacy_indicator.description = 'Indicator Description'
efficacy_indicator.save()
mock_get_efficacy_indicator.assert_called_once_with(
self.context, uuid)
mock_update_efficacy_indicator.assert_called_once_with(
uuid, {'description': 'Indicator Description'})
self.assertEqual(self.context, efficacy_indicator._context)
def test_refresh(self):
uuid = self.fake_efficacy_indicator['uuid']
returns = [dict(self.fake_efficacy_indicator,
description="first description"),
dict(self.fake_efficacy_indicator,
description="second description")]
expected = [mock.call(self.context, uuid),
mock.call(self.context, uuid)]
with mock.patch.object(self.dbapi, 'get_efficacy_indicator_by_uuid',
side_effect=returns,
autospec=True) as mock_get_efficacy_indicator:
efficacy_indicator = objects.EfficacyIndicator.get(
self.context, uuid)
self.assertEqual(
"first description", efficacy_indicator.description)
efficacy_indicator.refresh()
self.assertEqual(
"second description", efficacy_indicator.description)
self.assertEqual(
expected, mock_get_efficacy_indicator.call_args_list)
self.assertEqual(self.context, efficacy_indicator._context)