From 4c9fc5163be10a214f5f12999c1aa9ad9eb497ef Mon Sep 17 00:00:00 2001 From: yanyanhu Date: Tue, 24 Mar 2015 04:55:54 -0400 Subject: [PATCH] Initial version - Webhook DB APIs Change-Id: I4e91f0d5f211935d518a35254b665e9e682f2e2b --- senlin/common/consts.py | 7 + senlin/db/api.py | 34 +++ senlin/db/sqlalchemy/api.py | 73 ++++++ senlin/tests/db/shared.py | 18 ++ senlin/tests/db/test_webhook_api.py | 333 ++++++++++++++++++++++++++++ 5 files changed, 465 insertions(+) create mode 100644 senlin/tests/db/test_webhook_api.py diff --git a/senlin/common/consts.py b/senlin/common/consts.py index 288d5d16d..6b9ca0725 100644 --- a/senlin/common/consts.py +++ b/senlin/common/consts.py @@ -103,6 +103,13 @@ CLUSTER_POLICY_ATTRS = ( 'policy_id', 'priority', 'level', 'cooldown', 'enabled', ) +WEBHOOK_ATTRS = ( + WEBHOOK_NAME, WEBHOOK_OBJ_ID, WEBHOOK_OBJ_TYPE, + WEBHOOK_CREATED_TIME, WEBHOOK_DELETED_TIME, +) = ( + 'name', 'obj_id', 'obj_type', 'created_time', 'deleted_time', +) + EVENT_ATTRS = ( EVENT_TIMESTAMP, EVENT_OBJ_ID, EVENT_OBJ_NAME, EVENT_OBJ_TYPE, EVENT_USER, EVENT_ACTION, EVENT_STATUS, EVENT_STATUS_REASON, diff --git a/senlin/db/api.py b/senlin/db/api.py index a0c861379..551e8f619 100644 --- a/senlin/db/api.py +++ b/senlin/db/api.py @@ -291,6 +291,40 @@ def event_get_all_by_cluster(context, cluster_id, limit=None, marker=None, filters=filters) +# Webhooks +def webhook_create(context, values): + return IMPL.webhook_create(context, values) + + +def webhook_get(context, webhook_id, show_deleted=False): + return IMPL.webhook_get(context, webhook_id, show_deleted=show_deleted) + + +def webhook_get_by_name(context, name, show_deleted=False): + return IMPL.webhook_get_by_name(context, name, show_deleted=show_deleted) + + +def webhook_get_all(context, show_deleted=False, limit=None, + marker=None, sort_keys=None, sort_dir=None, + filters=None, tenant_safe=True): + return IMPL.webhook_get_all(context, show_deleted=show_deleted, + limit=limit, marker=marker, + sort_keys=sort_keys, sort_dir=sort_dir, + filters=filters, tenant_safe=tenant_safe) + + +def webhook_get_all_by_obj_id(context, obj_id): + return IMPL.webhook_get_all_by_obj_id(context, obj_id) + + +def webhook_get_all_by_obj_type(context, obj_type): + return IMPL.webhook_get_all_by_obj_type(context, obj_type) + + +def webhook_delete(context, webhook_id, force=False): + return IMPL.webhook_delete(context, webhook_id, force) + + # Actions def action_create(context, values): return IMPL.action_create(context, values) diff --git a/senlin/db/sqlalchemy/api.py b/senlin/db/sqlalchemy/api.py index 20a43db55..243d16379 100644 --- a/senlin/db/sqlalchemy/api.py +++ b/senlin/db/sqlalchemy/api.py @@ -472,6 +472,79 @@ def node_delete(context, node_id, force=False): session.flush() +# Webhooks +def webhook_create(context, values): + webhook = models.Webhook() + webhook.update(values) + webhook.save(_session(context)) + return webhook + + +def webhook_get(context, webhook_id, show_deleted=False): + webhook = model_query(context, models.Webhook).get(webhook_id) + if not webhook: + return None + + if not show_deleted and webhook.deleted_time is not None: + return None + + return webhook + + +def webhook_get_by_name(context, name, show_deleted=False): + return query_by_name(context, models.Webhook, name, + show_deleted=show_deleted) + + +def webhook_get_all(context, show_deleted=False, limit=None, + marker=None, sort_keys=None, sort_dir=None, + filters=None, tenant_safe=True): + query = soft_delete_aware_query(context, models.Webhook, + show_deleted=show_deleted) + + if tenant_safe: + query = query.filter_by(project=context.tenant_id) + + if filters is None: + filters = {} + + sort_key_map = { + consts.WEBHOOK_NAME: models.Webhook.name.key, + consts.WEBHOOK_OBJ_ID: models.Webhook.obj_id.key, + consts.WEBHOOK_OBJ_TYPE: models.Webhook.obj_type.key, + consts.WEBHOOK_CREATED_TIME: models.Webhook.created_time.key, + consts.WEBHOOK_DELETED_TIME: models.Webhook.deleted_time.key, + } + keys = _get_sort_keys(sort_keys, sort_key_map) + query = db_filters.exact_filter(query, models.Webhook, filters) + return _paginate_query(context, query, models.Webhook, + limit=limit, marker=marker, + sort_keys=keys, sort_dir=sort_dir, + default_sort_keys=['obj_id']).all() + + +def webhook_get_all_by_obj_id(context, obj_id): + query = model_query(context, models.Webhook).filter_by(obj_id=obj_id) + webhooks = query.all() + return webhooks + + +def webhook_get_all_by_obj_type(context, obj_type): + query = model_query(context, models.Webhook).filter_by(obj_type=obj_type) + webhooks = query.all() + return webhooks + + +def webhook_delete(context, webhook_id, force=False): + session = _session(context) + webhook = session.query(models.Webhook).get(webhook_id) + if not webhook: + return + + webhook.soft_delete(session=session) + session.flush() + + # Locks def cluster_lock_acquire(cluster_id, action_id, scope): '''Acquire lock on a cluster. diff --git a/senlin/tests/db/shared.py b/senlin/tests/db/shared.py index 3a436ef1f..f764b2aaa 100644 --- a/senlin/tests/db/shared.py +++ b/senlin/tests/db/shared.py @@ -100,6 +100,24 @@ def create_node(ctx, cluster, profile, **kwargs): return db_api.node_create(ctx, values) +def create_webhook(ctx, obj_id, obj_type, action, **kwargs): + values = { + 'name': 'test_webhook_name', + 'user': ctx.user, + 'project': ctx.tenant_id, + 'domain': ctx.domain, + 'created_time': None, + 'deleted_time': None, + 'obj_id': obj_id, + 'obj_type': obj_type, + 'action': action, + 'credential': None, + 'params': None, + } + values.update(kwargs) + return db_api.webhook_create(ctx, values) + + def create_action(ctx, **kwargs): values = { 'context': kwargs.get('context'), diff --git a/senlin/tests/db/test_webhook_api.py b/senlin/tests/db/test_webhook_api.py new file mode 100644 index 000000000..23943bd8c --- /dev/null +++ b/senlin/tests/db/test_webhook_api.py @@ -0,0 +1,333 @@ +# 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 + +from senlin.db.sqlalchemy import api as db_api +from senlin.tests.common import base +from senlin.tests.common import utils +from senlin.tests.db import shared + +UUID1 = shared.UUID1 +UUID2 = shared.UUID2 +UUID3 = shared.UUID3 + + +class DBAPIWebhookTest(base.SenlinTestCase): + def setUp(self): + super(DBAPIWebhookTest, self).setUp() + self.ctx = utils.dummy_context() + self.obj_id = UUID1 + self.obj_type = 'test_obj_type' + self.action = 'test_action' + + def test_webhook_create(self): + res = shared.create_webhook(self.ctx, self.obj_id, + self.obj_type, self.action) + webhook = db_api.webhook_get(self.ctx, res.id) + self.assertIsNotNone(webhook) + self.assertEqual(UUID1, webhook.obj_id) + self.assertEqual('test_webhook_name', webhook.name) + self.assertEqual(self.ctx.user, webhook.user) + self.assertEqual(self.ctx.domain, webhook.domain) + self.assertEqual(self.ctx.tenant_id, webhook.project) + self.assertIsNone(webhook.created_time) + self.assertIsNone(webhook.deleted_time) + self.assertEqual('test_obj_type', webhook.obj_type) + self.assertEqual('test_action', webhook.action) + + def test_webhook_get(self): + res = shared.create_webhook(self.ctx, self.obj_id, + self.obj_type, self.action) + webhook = db_api.webhook_get(self.ctx, res.id) + self.assertIsNotNone(webhook) + + def test_webhook_get_show_deleted(self): + res = shared.create_webhook(self.ctx, self.obj_id, + self.obj_type, self.action) + webhook_id = res.id + webhook = db_api.webhook_get(self.ctx, webhook_id) + self.assertIsNotNone(webhook) + + db_api.webhook_delete(self.ctx, webhook_id) + + webhook = db_api.webhook_get(self.ctx, webhook_id) + self.assertIsNone(webhook) + + webhook = db_api.webhook_get(self.ctx, webhook_id, show_deleted=False) + self.assertIsNone(webhook) + + webhook = db_api.webhook_get(self.ctx, webhook_id, show_deleted=True) + self.assertEqual(webhook_id, webhook.id) + + def test_webhook_get_by_name(self): + webhook_name = 'fake_webhook_name' + shared.create_webhook(self.ctx, self.obj_id, self.obj_type, + self.action, name=webhook_name) + webhook = db_api.webhook_get_by_name(self.ctx, webhook_name) + self.assertIsNotNone(webhook) + self.assertEqual(webhook_name, webhook.name) + + res = db_api.webhook_get_by_name(self.ctx, 'BogusName') + self.assertIsNone(res) + + def test_webhook_get_by_name_show_deleted(self): + webhook_name = 'fake_webhook_name' + shared.create_webhook(self.ctx, self.obj_id, self.obj_type, + self.action, name=webhook_name) + webhook = db_api.webhook_get_by_name(self.ctx, webhook_name) + self.assertIsNotNone(webhook) + self.assertEqual(webhook_name, webhook.name) + + webhook_id = webhook.id + db_api.webhook_delete(self.ctx, webhook_id) + + res = db_api.webhook_get_by_name(self.ctx, webhook_name) + self.assertIsNone(res) + + res = db_api.webhook_get_by_name(self.ctx, webhook_name, + show_deleted=False) + self.assertIsNone(res) + + res = db_api.webhook_get_by_name(self.ctx, webhook_name, + show_deleted=True) + self.assertEqual(webhook_id, res.id) + + def test_webhook_get_all(self): + values = [{'name': 'webhook1'}, + {'name': 'webhook2'}, + {'name': 'webhook3'}] + [shared.create_webhook( + self.ctx, self.obj_id, self.obj_type, + self.action, **v) for v in values] + + webhooks = db_api.webhook_get_all(self.ctx) + self.assertEqual(3, len(webhooks)) + + names = [webhook.name for webhook in webhooks] + [self.assertIn(val['name'], names) for val in values] + + def test_webhook_get_all_show_deleted(self): + values = [{'id': 'webhook1'}, {'id': 'webhook2'}, {'id': 'webhook3'}] + for v in values: + shared.create_webhook(self.ctx, self.obj_id, self.obj_type, + self.action, **v) + + db_api.webhook_delete(self.ctx, 'webhook2') + + webhooks = db_api.webhook_get_all(self.ctx) + self.assertEqual(2, len(webhooks)) + + webhooks = db_api.webhook_get_all(self.ctx, show_deleted=False) + self.assertEqual(2, len(webhooks)) + + webhooks = db_api.webhook_get_all(self.ctx, show_deleted=True) + self.assertEqual(3, len(webhooks)) + + def test_webhook_get_all_with_limit_marker(self): + webhook_ids = ['webhook1', 'webhook2', 'webhook3'] + for v in webhook_ids: + shared.create_webhook(self.ctx, self.obj_id, self.obj_type, + self.action, id=v, + created_time=datetime.datetime.utcnow()) + + webhooks = db_api.webhook_get_all(self.ctx, limit=1) + self.assertEqual(1, len(webhooks)) + + webhooks = db_api.webhook_get_all(self.ctx, limit=2) + self.assertEqual(2, len(webhooks)) + + webhooks = db_api.webhook_get_all(self.ctx, limit=5) + self.assertEqual(3, len(webhooks)) + + webhooks = db_api.webhook_get_all(self.ctx, marker='webhook1') + self.assertEqual(2, len(webhooks)) + + webhooks = db_api.webhook_get_all(self.ctx, marker='webhook2') + self.assertEqual(1, len(webhooks)) + + webhooks = db_api.webhook_get_all(self.ctx, marker='webhook3') + self.assertEqual(0, len(webhooks)) + + webhooks = db_api.webhook_get_all(self.ctx, limit=1, marker='webhook1') + self.assertEqual(1, len(webhooks)) + + def test_webhook_get_all_used_sort_keys(self): + webhook_ids = ['webhook1', 'webhook2', 'webhook3'] + for v in webhook_ids: + shared.create_webhook(self.ctx, self.obj_id, self.obj_type, + self.action, id=v) + + mock_paginate = self.patchobject(db_api.utils, 'paginate_query') + sort_keys = ['name', 'created_time', 'deleted_time', + 'obj_id', 'obj_type'] + + db_api.webhook_get_all(self.ctx, sort_keys=sort_keys) + args = mock_paginate.call_args[0] + used_sort_keys = set(args[3]) + expected_keys = set(['name', 'created_time', 'deleted_time', + 'obj_id', 'obj_type', 'id']) + self.assertEqual(expected_keys, used_sort_keys) + + def test_webhook_get_all_sort_keys_wont_change(self): + sort_keys = ['id'] + db_api.webhook_get_all(self.ctx, sort_keys=sort_keys) + self.assertEqual(['id'], sort_keys) + + def test_webhook_get_all_sort_keys_and_dir(self): + values = [{'id': '001', 'name': 'webhook1'}, + {'id': '002', 'name': 'webhook3'}, + {'id': '003', 'name': 'webhook2'}] + obj_ids = {'webhook1': 'id3', + 'webhook2': 'id2', + 'webhook3': 'id1'} + for v in values: + shared.create_webhook(self.ctx, obj_ids[v['name']], + self.obj_type, + self.action, **v) + + webhooks = db_api.webhook_get_all(self.ctx, + sort_keys=['name', 'obj_id'], + sort_dir='asc') + self.assertEqual(3, len(webhooks)) + # Sorted by name (ascending) + self.assertEqual('001', webhooks[0].id) + self.assertEqual('003', webhooks[1].id) + self.assertEqual('002', webhooks[2].id) + + webhooks = db_api.webhook_get_all(self.ctx, + sort_keys=['obj_id', 'name'], + sort_dir='asc') + self.assertEqual(3, len(webhooks)) + # Sorted by obj_id (ascending) + self.assertEqual('002', webhooks[0].id) + self.assertEqual('003', webhooks[1].id) + self.assertEqual('001', webhooks[2].id) + + webhooks = db_api.webhook_get_all(self.ctx, + sort_keys=['obj_id', 'name'], + sort_dir='desc') + self.assertEqual(3, len(webhooks)) + # Sorted by obj_id (descending) + self.assertEqual('001', webhooks[0].id) + self.assertEqual('003', webhooks[1].id) + self.assertEqual('002', webhooks[2].id) + + def test_webhook_get_all_default_sort_dir(self): + values = [{'id': '001', 'name': 'webhook1'}, + {'id': '002', 'name': 'webhook2'}, + {'id': '003', 'name': 'webhook3'}] + obj_ids = {'webhook1': 'id3', + 'webhook2': 'id2', + 'webhook3': 'id1'} + for v in values: + shared.create_webhook(self.ctx, obj_ids[v['name']], + self.obj_type, + self.action, **v) + + webhooks = db_api.webhook_get_all(self.ctx, sort_dir='asc') + self.assertEqual(3, len(webhooks)) + self.assertEqual(values[2]['id'], webhooks[0].id) + self.assertEqual(values[1]['id'], webhooks[1].id) + self.assertEqual(values[0]['id'], webhooks[2].id) + + def test_webhook_get_all_with_filters(self): + shared.create_webhook(self.ctx, self.obj_id, + self.obj_type, self.action, name='webhook1') + shared.create_webhook(self.ctx, self.obj_id, + self.obj_type, self.action, name='webhook2') + + filters = {'name': ['webhook1', 'webhookx']} + results = db_api.webhook_get_all(self.ctx, filters=filters) + self.assertEqual(1, len(results)) + self.assertEqual('webhook1', results[0]['name']) + + filters = {'name': 'webhook1'} + results = db_api.webhook_get_all(self.ctx, filters=filters) + self.assertEqual(1, len(results)) + self.assertEqual('webhook1', results[0]['name']) + + def test_webhook_get_all_with_empty_filters(self): + shared.create_webhook(self.ctx, self.obj_id, + self.obj_type, self.action, name='webhook1') + shared.create_webhook(self.ctx, self.obj_id, + self.obj_type, self.action, name='webhook2') + + filters = None + results = db_api.webhook_get_all(self.ctx, filters=filters) + self.assertEqual(2, len(results)) + + def test_webhook_get_all_with_tenant_safe(self): + shared.create_webhook(self.ctx, self.obj_id, + self.obj_type, self.action, name='webhook1') + shared.create_webhook(self.ctx, self.obj_id, + self.obj_type, self.action, name='webhook2') + + self.ctx.tenant_id = 'a-different-tenant' + results = db_api.webhook_get_all(self.ctx, tenant_safe=False) + self.assertEqual(2, len(results)) + + self.ctx.tenant_id = 'a-different-tenant' + results = db_api.webhook_get_all(self.ctx) + self.assertEqual(0, len(results)) + + results = db_api.webhook_get_all(self.ctx, tenant_safe=True) + self.assertEqual(0, len(results)) + + def test_webhook_get_by_obj_id(self): + obj_ids = [UUID1, UUID2] + for obj_id in obj_ids: + shared.create_webhook(self.ctx, obj_id, + self.obj_type, + self.action) + + webhooks = db_api.webhook_get_all_by_obj_id(self.ctx, obj_ids[0]) + + self.assertEqual(1, len(webhooks)) + self.assertEqual(obj_ids[0], webhooks[0].obj_id) + + webhooks = db_api.webhook_get_all_by_obj_id(self.ctx, None) + self.assertEqual(0, len(webhooks)) + + def test_webhook_get_by_obj_type(self): + obj_types = ['type1', 'type2'] + for obj_type in obj_types: + shared.create_webhook(self.ctx, self.obj_id, + obj_type, + self.action) + + webhooks = db_api.webhook_get_all_by_obj_type(self.ctx, obj_types[0]) + + self.assertEqual(1, len(webhooks)) + self.assertEqual(obj_types[0], webhooks[0].obj_type) + + webhooks = db_api.webhook_get_all_by_obj_type(self.ctx, None) + self.assertEqual(0, len(webhooks)) + + def test_webhook_delete(self): + res = shared.create_webhook(self.ctx, self.obj_id, + self.obj_type, self.action) + webhook_id = res.id + webhook = db_api.webhook_get(self.ctx, webhook_id) + self.assertIsNotNone(webhook) + + db_api.webhook_delete(self.ctx, webhook_id) + res = db_api.webhook_get(self.ctx, webhook_id) + self.assertIsNone(res) + + def test_webhook_delete_not_found(self): + webhook_id = 'BogusWebhookID' + res = db_api.webhook_delete(self.ctx, webhook_id) + self.assertIsNone(res) + + res = db_api.webhook_get(self.ctx, webhook_id) + self.assertIsNone(res)