dynamic action description

Add a new table to save the mapping
Add logic to update the table when action loading
Add logic to show the action description

Change-Id: Ia008a8715bcc666ab0fefe444ef612394c775e91
Implements: blueprint dynamic-action-description
This commit is contained in:
licanwei 2017-07-15 02:25:43 -07:00
parent 0ddfa278ef
commit a24b7f0b61
15 changed files with 771 additions and 0 deletions

View File

@ -0,0 +1,4 @@
---
features:
- Add description property for dynamic action. Admin can see detail information
of any specify action.

View File

@ -118,6 +118,9 @@ class Action(base.APIBase):
action_type = wtypes.text
"""Action type"""
description = wtypes.text
"""Action description"""
input_parameters = types.jsontype
"""One or more key/value pairs """
@ -141,6 +144,7 @@ class Action(base.APIBase):
setattr(self, field, kwargs.get(field, wtypes.Unset))
self.fields.append('action_plan_id')
self.fields.append('description')
setattr(self, 'action_plan_uuid', kwargs.get('action_plan_id',
wtypes.Unset))
@ -162,6 +166,14 @@ class Action(base.APIBase):
@classmethod
def convert_with_links(cls, action, expand=True):
action = Action(**action.as_dict())
try:
obj_action_desc = objects.ActionDescription.get_by_type(
pecan.request.context, action.action_type)
description = obj_action_desc.description
except exception.ActionDescriptionNotFound:
description = ""
setattr(action, 'description', description)
return cls._convert_with_links(action, pecan.request.host_url, expand)
@classmethod

44
watcher/applier/sync.py Normal file
View File

@ -0,0 +1,44 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 ZTE
#
# 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.applier.loading import default
from watcher.common import context
from watcher.common import exception
from watcher import objects
class Syncer(object):
"""Syncs all available actions with the Watcher DB"""
def sync(self):
ctx = context.make_context()
action_loader = default.DefaultActionLoader()
available_actions = action_loader.list_available()
for action_type in available_actions.keys():
load_action = action_loader.load(action_type)
load_description = load_action.get_description()
try:
action_desc = objects.ActionDescription.get_by_type(
ctx, action_type)
if action_desc.description != load_description:
action_desc.description = load_description
action_desc.save()
except exception.ActionDescriptionNotFound:
obj_action_desc = objects.ActionDescription(ctx)
obj_action_desc.action_type = action_type
obj_action_desc.description = load_description
obj_action_desc.create()

View File

@ -23,6 +23,7 @@ import sys
from oslo_log import log as logging
from watcher.applier import manager
from watcher.applier import sync
from watcher.common import service as watcher_service
from watcher import conf
@ -37,6 +38,9 @@ def main():
applier_service = watcher_service.Service(manager.ApplierManager)
syncer = sync.Syncer()
syncer.sync()
# Only 1 process
launcher = watcher_service.launch(CONF, applier_service)
launcher.wait()

View File

@ -426,6 +426,15 @@ class CronFormatIsInvalid(WatcherException):
msg_fmt = _("Provided cron is invalid: %(message)s")
class ActionDescriptionAlreadyExists(Conflict):
msg_fmt = _("An action description with type %(action_type)s is "
"already exist.")
class ActionDescriptionNotFound(ResourceNotFound):
msg_fmt = _("The action description %(action_id)s cannot be found.")
# Model
class ComputeResourceNotFound(WatcherException):

View File

@ -0,0 +1,32 @@
"""add action description table
Revision ID: d09a5945e4a0
Revises: d098df6021e2
Create Date: 2017-07-13 20:33:01.473711
"""
# revision identifiers, used by Alembic.
revision = 'd09a5945e4a0'
down_revision = 'd098df6021e2'
from alembic import op
import oslo_db
import sqlalchemy as sa
def upgrade():
op.create_table('action_descriptions',
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('deleted_at', sa.DateTime(), nullable=True),
sa.Column('deleted', oslo_db.sqlalchemy.types.SoftDeleteInteger(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('action_type', sa.String(length=255), nullable=False),
sa.Column('description', sa.String(length=255), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('action_type', name='uniq_action_description0action_type')
)
def downgrade():
op.drop_table('action_descriptions')

View File

@ -1127,3 +1127,74 @@ class Connection(api.BaseConnection):
return self._soft_delete(models.Service, service_id)
except exception.ResourceNotFound:
raise exception.ServiceNotFound(service=service_id)
# ### ACTION_DESCRIPTIONS ### #
def _add_action_descriptions_filters(self, query, filters):
if not filters:
filters = {}
plain_fields = ['id', 'action_type']
return self._add_filters(
query=query, model=models.ActionDescription, filters=filters,
plain_fields=plain_fields)
def get_action_description_list(self, context, filters=None, limit=None,
marker=None, sort_key=None,
sort_dir=None, eager=False):
query = model_query(models.ActionDescription)
if eager:
query = self._set_eager_options(models.ActionDescription, query)
query = self._add_action_descriptions_filters(query, filters)
if not context.show_deleted:
query = query.filter_by(deleted_at=None)
return _paginate_query(models.ActionDescription, limit, marker,
sort_key, sort_dir, query)
def create_action_description(self, values):
try:
action_description = self._create(models.ActionDescription, values)
except db_exc.DBDuplicateEntry:
raise exception.ActionDescriptionAlreadyExists(
action_type=values['action_type'])
return action_description
def _get_action_description(self, context, fieldname, value, eager):
try:
return self._get(context, model=models.ActionDescription,
fieldname=fieldname, value=value, eager=eager)
except exception.ResourceNotFound:
raise exception.ActionDescriptionNotFound(action_id=value)
def get_action_description_by_id(self, context,
action_id, eager=False):
return self._get_action_description(
context, fieldname="id", value=action_id, eager=eager)
def get_action_description_by_type(self, context,
action_type, eager=False):
return self._get_action_description(
context, fieldname="action_type", value=action_type, eager=eager)
def destroy_action_description(self, action_id):
try:
return self._destroy(models.ActionDescription, action_id)
except exception.ResourceNotFound:
raise exception.ActionDescriptionNotFound(
action_id=action_id)
def update_action_description(self, action_id, values):
try:
return self._update(models.ActionDescription,
action_id, values)
except exception.ResourceNotFound:
raise exception.ActionDescriptionNotFound(
action_id=action_id)
def soft_delete_action_description(self, action_id):
try:
return self._soft_delete(models.ActionDescription, action_id)
except exception.ResourceNotFound:
raise exception.ActionDescriptionNotFound(
action_id=action_id)

View File

@ -278,3 +278,17 @@ class Service(Base):
name = Column(String(255), nullable=False)
host = Column(String(255), nullable=False)
last_seen_up = Column(DateTime, nullable=True)
class ActionDescription(Base):
"""Represents a action description"""
__tablename__ = 'action_descriptions'
__table_args__ = (
UniqueConstraint('action_type',
name="uniq_action_description0action_type"),
table_args()
)
id = Column(Integer, primary_key=True)
action_type = Column(String(255), nullable=False)
description = Column(String(255), nullable=False)

View File

@ -33,3 +33,4 @@ def register_all():
__import__('watcher.objects.efficacy_indicator')
__import__('watcher.objects.scoring_engine')
__import__('watcher.objects.service')
__import__('watcher.objects.action_description')

View File

@ -0,0 +1,141 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 ZTE
#
# 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 db_api
from watcher.objects import base
from watcher.objects import fields as wfields
@base.WatcherObjectRegistry.register
class ActionDescription(base.WatcherPersistentObject, base.WatcherObject,
base.WatcherObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = db_api.get_instance()
fields = {
'id': wfields.IntegerField(),
'action_type': wfields.StringField(),
'description': wfields.StringField(),
}
@base.remotable_classmethod
def get(cls, context, action_id):
"""Find a action description based on its id
: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
:param action_id: the id of a action description.
:returns: a :class:`ActionDescription` object.
"""
if utils.is_int_like(action_id):
db_action = cls.dbapi.get_action_description_by_id(
context, action_id)
action = ActionDescription._from_db_object(cls(context), db_action)
return action
else:
raise exception.InvalidIdentity(identity=action_id)
@base.remotable_classmethod
def get_by_type(cls, context, action_type):
"""Find a action description based on action type
:param action_type: the action type of a action description.
:param context: Security context
:returns: a :class:`ActionDescription` object.
"""
db_action = cls.dbapi.get_action_description_by_type(
context, action_type)
action = cls._from_db_object(cls(context), db_action)
return action
@base.remotable_classmethod
def list(cls, context, limit=None, marker=None, filters=None,
sort_key=None, sort_dir=None):
"""Return a list of :class:`ActionDescription` objects.
: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.: ActionDescription(context)
:param filters: dict mapping the filter key to a value.
:param limit: maximum number of resources to return in a single result.
:param marker: pagination marker for large data sets.
:param sort_key: column to sort results by.
:param sort_dir: direction to sort. "asc" or "desc".
:returns: a list of :class:`ActionDescription` object.
"""
db_actions = cls.dbapi.get_action_description_list(
context,
filters=filters,
limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return [cls._from_db_object(cls(context), obj) for obj in db_actions]
@base.remotable
def create(self):
"""Create a :class:`ActionDescription` record in the DB."""
values = self.obj_get_changes()
db_action = self.dbapi.create_action_description(values)
self._from_db_object(self, db_action)
@base.remotable
def save(self):
"""Save updates to this :class:`ActionDescription`.
Updates will be made column by column based on the result
of self.what_changed().
"""
updates = self.obj_get_changes()
db_obj = self.dbapi.update_action_description(self.id, updates)
obj = self._from_db_object(self, db_obj, eager=False)
self.obj_refresh(obj)
self.obj_reset_changes()
def refresh(self):
"""Loads updates for this :class:`ActionDescription`.
Loads a action description with the same id from the database and
checks for updated attributes. Updates are applied from
the loaded action description column by column, if there
are any updates.
"""
current = self.get(self._context, action_id=self.id)
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):
"""Soft Delete the :class:`ActionDescription` from the DB."""
db_obj = self.dbapi.soft_delete_action_description(self.id)
obj = self._from_db_object(
self.__class__(self._context), db_obj, eager=False)
self.obj_refresh(obj)

View File

@ -22,6 +22,7 @@ import types
import mock
from oslo_config import cfg
from oslo_service import service
from watcher.applier import sync
from watcher.common import service as watcher_service
from watcher.cmd import applier
@ -49,6 +50,7 @@ class TestApplier(base.BaseTestCase):
super(TestApplier, self).tearDown()
self.conf._parse_cli_opts = self._parse_cli_opts
@mock.patch.object(sync.Syncer, "sync", mock.Mock())
@mock.patch.object(service, "launch")
def test_run_applier_app(self, m_launch):
applier.main()

View File

@ -0,0 +1,293 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 ZTE
#
# 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.
"""Tests for manipulating ActionDescription via the DB API"""
import freezegun
from watcher.common import exception
from watcher.tests.db import base
from watcher.tests.db import utils
class TestDbActionDescriptionFilters(base.DbTestCase):
FAKE_OLDER_DATE = '2015-01-01T09:52:05.219414'
FAKE_OLD_DATE = '2016-01-01T09:52:05.219414'
FAKE_TODAY = '2017-02-24T09:52:05.219414'
def setUp(self):
super(TestDbActionDescriptionFilters, self).setUp()
self.context.show_deleted = True
self._data_setup()
def _data_setup(self):
action_desc1_type = "nop"
action_desc2_type = "sleep"
action_desc3_type = "resize"
with freezegun.freeze_time(self.FAKE_TODAY):
self.action_desc1 = utils.create_test_action_desc(
id=1, action_type=action_desc1_type,
description="description")
with freezegun.freeze_time(self.FAKE_OLD_DATE):
self.action_desc2 = utils.create_test_action_desc(
id=2, action_type=action_desc2_type,
description="description")
with freezegun.freeze_time(self.FAKE_OLDER_DATE):
self.action_desc3 = utils.create_test_action_desc(
id=3, action_type=action_desc3_type,
description="description")
def _soft_delete_action_descs(self):
with freezegun.freeze_time(self.FAKE_TODAY):
self.dbapi.soft_delete_action_description(self.action_desc1.id)
with freezegun.freeze_time(self.FAKE_OLD_DATE):
self.dbapi.soft_delete_action_description(self.action_desc2.id)
with freezegun.freeze_time(self.FAKE_OLDER_DATE):
self.dbapi.soft_delete_action_description(self.action_desc3.id)
def _update_action_descs(self):
with freezegun.freeze_time(self.FAKE_TODAY):
self.dbapi.update_action_description(
self.action_desc1.id, values={"description":
"nop description"})
with freezegun.freeze_time(self.FAKE_OLD_DATE):
self.dbapi.update_action_description(
self.action_desc2.id, values={"description":
"sleep description"})
with freezegun.freeze_time(self.FAKE_OLDER_DATE):
self.dbapi.update_action_description(
self.action_desc3.id, values={"description":
"resize description"})
def test_get_action_desc_list_filter_deleted_true(self):
with freezegun.freeze_time(self.FAKE_TODAY):
self.dbapi.soft_delete_action_description(self.action_desc1.id)
res = self.dbapi.get_action_description_list(
self.context, filters={'deleted': True})
self.assertEqual([self.action_desc1['action_type']],
[r.action_type for r in res])
def test_get_action_desc_list_filter_deleted_false(self):
with freezegun.freeze_time(self.FAKE_TODAY):
self.dbapi.soft_delete_action_description(self.action_desc1.id)
res = self.dbapi.get_action_description_list(
self.context, filters={'deleted': False})
self.assertEqual(
set([self.action_desc2['action_type'],
self.action_desc3['action_type']]),
set([r.action_type for r in res]))
def test_get_action_desc_list_filter_deleted_at_eq(self):
self._soft_delete_action_descs()
res = self.dbapi.get_action_description_list(
self.context, filters={'deleted_at__eq': self.FAKE_TODAY})
self.assertEqual([self.action_desc1['id']], [r.id for r in res])
def test_get_action_desc_list_filter_deleted_at_lt(self):
self._soft_delete_action_descs()
res = self.dbapi.get_action_description_list(
self.context, filters={'deleted_at__lt': self.FAKE_TODAY})
self.assertEqual(
set([self.action_desc2['id'], self.action_desc3['id']]),
set([r.id for r in res]))
def test_get_action_desc_list_filter_deleted_at_lte(self):
self._soft_delete_action_descs()
res = self.dbapi.get_action_description_list(
self.context, filters={'deleted_at__lte': self.FAKE_OLD_DATE})
self.assertEqual(
set([self.action_desc2['id'], self.action_desc3['id']]),
set([r.id for r in res]))
def test_get_action_desc_list_filter_deleted_at_gt(self):
self._soft_delete_action_descs()
res = self.dbapi.get_action_description_list(
self.context, filters={'deleted_at__gt': self.FAKE_OLD_DATE})
self.assertEqual([self.action_desc1['id']], [r.id for r in res])
def test_get_action_desc_list_filter_deleted_at_gte(self):
self._soft_delete_action_descs()
res = self.dbapi.get_action_description_list(
self.context, filters={'deleted_at__gte': self.FAKE_OLD_DATE})
self.assertEqual(
set([self.action_desc1['id'], self.action_desc2['id']]),
set([r.id for r in res]))
# created_at #
def test_get_action_desc_list_filter_created_at_eq(self):
res = self.dbapi.get_action_description_list(
self.context, filters={'created_at__eq': self.FAKE_TODAY})
self.assertEqual([self.action_desc1['id']], [r.id for r in res])
def test_get_action_desc_list_filter_created_at_lt(self):
res = self.dbapi.get_action_description_list(
self.context, filters={'created_at__lt': self.FAKE_TODAY})
self.assertEqual(
set([self.action_desc2['id'], self.action_desc3['id']]),
set([r.id for r in res]))
def test_get_action_desc_list_filter_created_at_lte(self):
res = self.dbapi.get_action_description_list(
self.context, filters={'created_at__lte': self.FAKE_OLD_DATE})
self.assertEqual(
set([self.action_desc2['id'], self.action_desc3['id']]),
set([r.id for r in res]))
def test_get_action_desc_list_filter_created_at_gt(self):
res = self.dbapi.get_action_description_list(
self.context, filters={'created_at__gt': self.FAKE_OLD_DATE})
self.assertEqual([self.action_desc1['id']], [r.id for r in res])
def test_get_action_desc_list_filter_created_at_gte(self):
res = self.dbapi.get_action_description_list(
self.context, filters={'created_at__gte': self.FAKE_OLD_DATE})
self.assertEqual(
set([self.action_desc1['id'], self.action_desc2['id']]),
set([r.id for r in res]))
# updated_at #
def test_get_action_desc_list_filter_updated_at_eq(self):
self._update_action_descs()
res = self.dbapi.get_action_description_list(
self.context, filters={'updated_at__eq': self.FAKE_TODAY})
self.assertEqual([self.action_desc1['id']], [r.id for r in res])
def test_get_action_desc_list_filter_updated_at_lt(self):
self._update_action_descs()
res = self.dbapi.get_action_description_list(
self.context, filters={'updated_at__lt': self.FAKE_TODAY})
self.assertEqual(
set([self.action_desc2['id'], self.action_desc3['id']]),
set([r.id for r in res]))
def test_get_action_desc_list_filter_updated_at_lte(self):
self._update_action_descs()
res = self.dbapi.get_action_description_list(
self.context, filters={'updated_at__lte': self.FAKE_OLD_DATE})
self.assertEqual(
set([self.action_desc2['id'], self.action_desc3['id']]),
set([r.id for r in res]))
def test_get_action_desc_list_filter_updated_at_gt(self):
self._update_action_descs()
res = self.dbapi.get_action_description_list(
self.context, filters={'updated_at__gt': self.FAKE_OLD_DATE})
self.assertEqual([self.action_desc1['id']], [r.id for r in res])
def test_get_action_desc_list_filter_updated_at_gte(self):
self._update_action_descs()
res = self.dbapi.get_action_description_list(
self.context, filters={'updated_at__gte': self.FAKE_OLD_DATE})
self.assertEqual(
set([self.action_desc1['id'], self.action_desc2['id']]),
set([r.id for r in res]))
class DbActionDescriptionTestCase(base.DbTestCase):
def _create_test_action_desc(self, **kwargs):
action_desc = utils.get_test_action_desc(**kwargs)
self.dbapi.create_action_description(action_desc)
return action_desc
def test_get_action_desc_list(self):
ids = []
for i in range(1, 4):
action_desc = utils.create_test_action_desc(
id=i,
action_type="action_%s" % i,
description="description_{0}".format(i))
ids.append(action_desc['id'])
action_descs = self.dbapi.get_action_description_list(self.context)
action_desc_ids = [s.id for s in action_descs]
self.assertEqual(sorted(ids), sorted(action_desc_ids))
def test_get_action_desc_list_with_filters(self):
action_desc1 = self._create_test_action_desc(
id=1,
action_type="action_1",
description="description_1",
)
action_desc2 = self._create_test_action_desc(
id=2,
action_type="action_2",
description="description_2",
)
res = self.dbapi.get_action_description_list(
self.context, filters={'action_type': 'action_1'})
self.assertEqual([action_desc1['id']], [r.id for r in res])
res = self.dbapi.get_action_description_list(
self.context, filters={'action_type': 'action_3'})
self.assertEqual([], [r.id for r in res])
res = self.dbapi.get_action_description_list(
self.context,
filters={'action_type': 'action_2'})
self.assertEqual([action_desc2['id']], [r.id for r in res])
def test_get_action_desc_by_type(self):
created_action_desc = self._create_test_action_desc()
action_desc = self.dbapi.get_action_description_by_type(
self.context, created_action_desc['action_type'])
self.assertEqual(action_desc.action_type,
created_action_desc['action_type'])
def test_get_action_desc_that_does_not_exist(self):
self.assertRaises(exception.ActionDescriptionNotFound,
self.dbapi.get_action_description_by_id,
self.context, 404)
def test_update_action_desc(self):
action_desc = self._create_test_action_desc()
res = self.dbapi.update_action_description(
action_desc['id'], {'description': 'description_test'})
self.assertEqual('description_test', res.description)

View File

@ -331,3 +331,26 @@ def create_test_efficacy_indicator(**kwargs):
del efficacy_indicator['id']
dbapi = db_api.get_instance()
return dbapi.create_efficacy_indicator(efficacy_indicator)
def get_test_action_desc(**kwargs):
return {
'id': kwargs.get('id', 1),
'action_type': kwargs.get('action_type', 'nop'),
'description': kwargs.get('description', 'Logging a NOP message'),
'created_at': kwargs.get('created_at'),
'updated_at': kwargs.get('updated_at'),
'deleted_at': kwargs.get('deleted_at'),
}
def create_test_action_desc(**kwargs):
"""Create test action description entry in DB and return ActionDescription.
Function to be used to create test ActionDescription objects in the DB.
:param kwargs: kwargs with overriding values for service's attributes.
:returns: Test ActionDescription DB object.
"""
action_desc = get_test_action_desc(**kwargs)
dbapi = db_api.get_instance()
return dbapi.create_action_description(action_desc)

View File

@ -0,0 +1,120 @@
# -*- encoding: utf-8 -*-
# Copyright 2017 ZTE
# 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 datetime
import iso8601
import mock
from watcher.db.sqlalchemy import api as db_api
from watcher import objects
from watcher.tests.db import base
from watcher.tests.db import utils
class TestActionDescriptionObject(base.DbTestCase):
def setUp(self):
super(TestActionDescriptionObject, self).setUp()
self.fake_action_desc = utils.get_test_action_desc(
created_at=datetime.datetime.utcnow())
@mock.patch.object(db_api.Connection, 'get_action_description_by_id')
def test_get_by_id(self, mock_get_action_desc):
action_desc_id = self.fake_action_desc['id']
mock_get_action_desc.return_value = self.fake_action_desc
action_desc = objects.ActionDescription.get(
self.context, action_desc_id)
mock_get_action_desc.assert_called_once_with(
self.context, action_desc_id)
self.assertEqual(self.context, action_desc._context)
@mock.patch.object(db_api.Connection, 'get_action_description_list')
def test_list(self, mock_get_list):
mock_get_list.return_value = [self.fake_action_desc]
action_desc = objects.ActionDescription.list(self.context)
self.assertEqual(1, mock_get_list.call_count)
self.assertEqual(1, len(action_desc))
self.assertIsInstance(action_desc[0], objects.ActionDescription)
self.assertEqual(self.context, action_desc[0]._context)
@mock.patch.object(db_api.Connection, 'create_action_description')
def test_create(self, mock_create_action_desc):
mock_create_action_desc.return_value = self.fake_action_desc
action_desc = objects.ActionDescription(
self.context, **self.fake_action_desc)
action_desc.create()
expected_action_desc = self.fake_action_desc.copy()
expected_action_desc['created_at'] = expected_action_desc[
'created_at'].replace(tzinfo=iso8601.iso8601.Utc())
mock_create_action_desc.assert_called_once_with(expected_action_desc)
self.assertEqual(self.context, action_desc._context)
@mock.patch.object(db_api.Connection, 'update_action_description')
@mock.patch.object(db_api.Connection, 'get_action_description_by_id')
def test_save(self, mock_get_action_desc, mock_update_action_desc):
mock_get_action_desc.return_value = self.fake_action_desc
fake_saved_action_desc = self.fake_action_desc.copy()
fake_saved_action_desc['updated_at'] = datetime.datetime.utcnow()
mock_update_action_desc.return_value = fake_saved_action_desc
_id = self.fake_action_desc['id']
action_desc = objects.ActionDescription.get(self.context, _id)
action_desc.description = 'This is a test'
action_desc.save()
mock_get_action_desc.assert_called_once_with(self.context, _id)
mock_update_action_desc.assert_called_once_with(
_id, {'description': 'This is a test'})
self.assertEqual(self.context, action_desc._context)
@mock.patch.object(db_api.Connection, 'get_action_description_by_id')
def test_refresh(self, mock_get_action_desc):
returns = [dict(self.fake_action_desc, description="Test message1"),
dict(self.fake_action_desc, description="Test message2")]
mock_get_action_desc.side_effect = returns
_id = self.fake_action_desc['id']
expected = [mock.call(self.context, _id),
mock.call(self.context, _id)]
action_desc = objects.ActionDescription.get(self.context, _id)
self.assertEqual("Test message1", action_desc.description)
action_desc.refresh()
self.assertEqual("Test message2", action_desc.description)
self.assertEqual(expected, mock_get_action_desc.call_args_list)
self.assertEqual(self.context, action_desc._context)
@mock.patch.object(db_api.Connection, 'soft_delete_action_description')
@mock.patch.object(db_api.Connection, 'get_action_description_by_id')
def test_soft_delete(self, mock_get_action_desc, mock_soft_delete):
mock_get_action_desc.return_value = self.fake_action_desc
fake_deleted_action_desc = self.fake_action_desc.copy()
fake_deleted_action_desc['deleted_at'] = datetime.datetime.utcnow()
mock_soft_delete.return_value = fake_deleted_action_desc
expected_action_desc = fake_deleted_action_desc.copy()
expected_action_desc['created_at'] = expected_action_desc[
'created_at'].replace(tzinfo=iso8601.iso8601.Utc())
expected_action_desc['deleted_at'] = expected_action_desc[
'deleted_at'].replace(tzinfo=iso8601.iso8601.Utc())
_id = self.fake_action_desc['id']
action_desc = objects.ActionDescription.get(self.context, _id)
action_desc.soft_delete()
mock_get_action_desc.assert_called_once_with(self.context, _id)
mock_soft_delete.assert_called_once_with(_id)
self.assertEqual(self.context, action_desc._context)
self.assertEqual(expected_action_desc, action_desc.as_dict())

View File

@ -419,6 +419,7 @@ expected_object_fingerprints = {
'ScoringEngine': '1.0-4abbe833544000728e17bd9e83f97576',
'Service': '1.0-4b35b99ada9677a882c9de2b30212f35',
'MyObj': '1.5-23c516d1e842f365f694e688d34e47c3',
'ActionDescription': '1.0-5761a3d16651046e7a0c357b57a6583e'
}