Merge "Convert db exceptions to api exceptions"

This commit is contained in:
Jenkins 2015-01-22 19:30:44 +00:00 committed by Gerrit Code Review
commit ede962ee00
21 changed files with 589 additions and 60 deletions

View File

@ -24,6 +24,7 @@ import wsmeext.pecan as wsme_pecan
import storyboard.api.auth.authorization_checks as checks
from storyboard.api.v1 import validations
from storyboard.api.v1 import wmodels
from storyboard.common import decorators
import storyboard.common.exception as exc
from storyboard.db.api import project_groups
from storyboard.db.api import projects
@ -38,6 +39,7 @@ class ProjectsSubcontroller(rest.RestController):
Project Group.
"""
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Project], int)
def get(self, project_group_id):
@ -55,6 +57,7 @@ class ProjectsSubcontroller(rest.RestController):
return [wmodels.Project.from_db_model(project)
for project in project_group.projects]
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.Project, int, int)
def put(self, project_group_id, project_id):
@ -65,6 +68,7 @@ class ProjectsSubcontroller(rest.RestController):
return wmodels.Project.from_db_model(projects.project_get(project_id))
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(None, int, int)
def delete(self, project_group_id, project_id):
@ -87,6 +91,7 @@ class ProjectGroupsController(rest.RestController):
validation_post_schema = validations.PROJECT_GROUPS_POST_SCHEMA
validation_put_schema = validations.PROJECT_GROUPS_PUT_SCHEMA
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.ProjectGroup, int)
def get_one(self, project_group_id):
@ -103,6 +108,7 @@ class ProjectGroupsController(rest.RestController):
return wmodels.ProjectGroup.from_db_model(group)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.ProjectGroup], int, int, unicode, unicode,
unicode, unicode)
@ -135,6 +141,7 @@ class ProjectGroupsController(rest.RestController):
return [wmodels.ProjectGroup.from_db_model(group) for group in groups]
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.ProjectGroup, body=wmodels.ProjectGroup)
def post(self, project_group):
@ -151,6 +158,7 @@ class ProjectGroupsController(rest.RestController):
return wmodels.ProjectGroup.from_db_model(created_group)
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.ProjectGroup, int, body=wmodels.ProjectGroup)
def put(self, project_group_id, project_group):
@ -170,6 +178,7 @@ class ProjectGroupsController(rest.RestController):
return wmodels.ProjectGroup.from_db_model(updated_group)
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(None, int)
def delete(self, project_group_id):

View File

@ -25,9 +25,11 @@ from storyboard.api.auth import authorization_checks as checks
from storyboard.api.v1.search import search_engine
from storyboard.api.v1 import validations
from storyboard.api.v1 import wmodels
from storyboard.common import decorators
from storyboard.db.api import projects as projects_api
from storyboard.openstack.common.gettextutils import _ # noqa
CONF = cfg.CONF
SEARCH_ENGINE = search_engine.get_engine()
@ -44,6 +46,7 @@ class ProjectsController(rest.RestController):
validation_post_schema = validations.PROJECTS_POST_SCHEMA
validation_put_schema = validations.PROJECTS_PUT_SCHEMA
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.Project, int)
def get_one_by_id(self, project_id):
@ -60,6 +63,7 @@ class ProjectsController(rest.RestController):
raise ClientSideError(_("Project %s not found") % project_id,
status_code=404)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.Project, unicode)
def get_one_by_name(self, project_name):
@ -76,6 +80,7 @@ class ProjectsController(rest.RestController):
raise ClientSideError(_("Project %s not found") % project_name,
status_code=404)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Project], int, int, unicode, unicode, int,
unicode, unicode)
@ -121,6 +126,7 @@ class ProjectsController(rest.RestController):
return [wmodels.Project.from_db_model(p) for p in projects]
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.Project, body=wmodels.Project)
def post(self, project):
@ -128,9 +134,11 @@ class ProjectsController(rest.RestController):
:param project: a project within the request body.
"""
result = projects_api.project_create(project.as_dict())
return wmodels.Project.from_db_model(result)
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.Project, int, body=wmodels.Project)
def put(self, project_id, project):
@ -155,6 +163,7 @@ class ProjectsController(rest.RestController):
except ValueError:
return False
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Project], unicode, unicode, int, int)
def search(self, q="", marker=None, limit=None):

View File

@ -28,6 +28,7 @@ from storyboard.api.v1.timeline import CommentsController
from storyboard.api.v1.timeline import TimeLineEventsController
from storyboard.api.v1 import validations
from storyboard.api.v1 import wmodels
from storyboard.common import decorators
from storyboard.db.api import stories as stories_api
from storyboard.db.api import timeline_events as events_api
from storyboard.openstack.common.gettextutils import _ # noqa
@ -46,6 +47,7 @@ class StoriesController(rest.RestController):
validation_post_schema = validations.STORIES_POST_SCHEMA
validation_put_schema = validations.STORIES_PUT_SCHEMA
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.Story, int)
def get_one(self, story_id):
@ -61,6 +63,7 @@ class StoriesController(rest.RestController):
raise ClientSideError(_("Story %s not found") % story_id,
status_code=404)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Story], unicode, unicode, [unicode], int,
int, int, int, int, unicode, unicode)
@ -115,6 +118,7 @@ class StoriesController(rest.RestController):
return [wmodels.Story.from_db_model(s) for s in stories]
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.Story, body=wmodels.Story)
def post(self, story):
@ -132,6 +136,7 @@ class StoriesController(rest.RestController):
return wmodels.Story.from_db_model(created_story)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.Story, int, body=wmodels.Story)
def put(self, story_id, story):
@ -154,6 +159,7 @@ class StoriesController(rest.RestController):
raise ClientSideError(_("Story %s not found") % story_id,
status_code=404)
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.Story, int)
def delete(self, story_id):
@ -168,6 +174,7 @@ class StoriesController(rest.RestController):
comments = CommentsController()
events = TimeLineEventsController()
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Story], unicode, unicode, int, int)
def search(self, q="", marker=None, limit=None):

View File

@ -24,6 +24,7 @@ import wsmeext.pecan as wsme_pecan
from storyboard.api.auth import authorization_checks as checks
from storyboard.api.v1 import base
from storyboard.common import decorators
from storyboard.db.api import subscription_events as subscription_events_api
from storyboard.db.api import users as user_api
from storyboard.openstack.common.gettextutils import _ # noqa
@ -69,6 +70,7 @@ class SubscriptionEventsController(rest.RestController):
subscriptionEvents.
"""
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(SubscriptionEvent, int)
def get_one(self, subscription_event_id):
@ -86,6 +88,7 @@ class SubscriptionEventsController(rest.RestController):
return SubscriptionEvent.from_db_model(subscription_event)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose([SubscriptionEvent], int, int, unicode,
int, unicode, unicode)
@ -136,6 +139,7 @@ class SubscriptionEventsController(rest.RestController):
return [SubscriptionEvent.from_db_model(s) for s in subscriptions]
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(None, int)
def delete(self, subscription_event_id):

View File

@ -24,6 +24,7 @@ import wsmeext.pecan as wsme_pecan
from storyboard.api.auth import authorization_checks as checks
from storyboard.api.v1 import base
from storyboard.common import decorators
from storyboard.db.api import subscriptions as subscription_api
from storyboard.db.api import users as user_api
from storyboard.openstack.common.gettextutils import _ # noqa
@ -62,6 +63,7 @@ class SubscriptionsController(rest.RestController):
Provides Create, Delete, and search methods for resource subscriptions.
"""
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(Subscription, int)
def get_one(self, subscription_id):
@ -79,6 +81,7 @@ class SubscriptionsController(rest.RestController):
return Subscription.from_db_model(subscription)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose([Subscription], int, int, [unicode], int, int,
unicode, unicode)
@ -130,6 +133,7 @@ class SubscriptionsController(rest.RestController):
return [Subscription.from_db_model(s) for s in subscriptions]
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(Subscription, body=Subscription)
def post(self, subscription):
@ -171,6 +175,7 @@ class SubscriptionsController(rest.RestController):
result = subscription_api.subscription_create(subscription.as_dict())
return Subscription.from_db_model(result)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(None, int)
def delete(self, subscription_id):

View File

@ -25,6 +25,7 @@ from storyboard.api.auth import authorization_checks as checks
from storyboard.api.v1.search import search_engine
from storyboard.api.v1 import validations
from storyboard.api.v1 import wmodels
from storyboard.common import decorators
from storyboard.db.api import tasks as tasks_api
from storyboard.db.api import timeline_events as events_api
from storyboard.openstack.common.gettextutils import _ # noqa
@ -42,6 +43,7 @@ class TasksController(rest.RestController):
validation_post_schema = validations.TASKS_POST_SCHEMA
validation_put_schema = validations.TASKS_PUT_SCHEMA
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.Task, int)
def get_one(self, task_id):
@ -57,6 +59,7 @@ class TasksController(rest.RestController):
raise ClientSideError(_("Task %s not found") % task_id,
status_code=404)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Task], unicode, int, int, int, int,
[unicode], [unicode], int, int, unicode, unicode)
@ -116,6 +119,7 @@ class TasksController(rest.RestController):
return [wmodels.Task.from_db_model(s) for s in tasks]
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.Task, body=wmodels.Task)
def post(self, task):
@ -136,6 +140,7 @@ class TasksController(rest.RestController):
return wmodels.Task.from_db_model(created_task)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.Task, int, body=wmodels.Task)
def put(self, task_id, task):
@ -200,6 +205,7 @@ class TasksController(rest.RestController):
task_title=original_task.title,
author_id=author_id)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.Task, int)
def delete(self, task_id):
@ -219,6 +225,7 @@ class TasksController(rest.RestController):
response.status_code = 204
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Task], unicode, unicode, int, int)
def search(self, q="", marker=None, limit=None):

View File

@ -25,6 +25,7 @@ import wsmeext.pecan as wsme_pecan
from storyboard.api.auth import authorization_checks as checks
from storyboard.api.v1 import validations
from storyboard.api.v1 import wmodels
from storyboard.common import decorators
from storyboard.common import exception as exc
from storyboard.db.api import teams as teams_api
from storyboard.db.api import users as users_api
@ -36,6 +37,7 @@ CONF = cfg.CONF
class UsersSubcontroller(rest.RestController):
"""This controller should be used to list, add or remove users from a Team.
"""
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.User], int)
def get(self, team_id):
@ -51,6 +53,7 @@ class UsersSubcontroller(rest.RestController):
return [wmodels.User.from_db_model(user) for user in team.users]
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.User, int, int)
def put(self, team_id, user_id):
@ -61,6 +64,7 @@ class UsersSubcontroller(rest.RestController):
return wmodels.User.from_db_model(user)
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(None, int, int)
def delete(self, team_id, user_id):
@ -76,6 +80,7 @@ class TeamsController(rest.RestController):
validation_post_schema = validations.TEAMS_POST_SCHEMA
validation_put_schema = validations.TEAMS_PUT_SCHEMA
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.Team, int)
def get_one_by_id(self, team_id):
@ -92,6 +97,7 @@ class TeamsController(rest.RestController):
raise ClientSideError(_("Team %s not found") % team_id,
status_code=404)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.Team, unicode)
def get_one_by_name(self, team_name):
@ -108,6 +114,7 @@ class TeamsController(rest.RestController):
raise ClientSideError(_("Team %s not found") % team_name,
status_code=404)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Team], int, int, unicode, unicode, unicode,
unicode)
@ -148,6 +155,7 @@ class TeamsController(rest.RestController):
return [wmodels.Team.from_db_model(t) for t in teams]
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.Team, body=wmodels.Team)
def post(self, team):
@ -158,6 +166,7 @@ class TeamsController(rest.RestController):
result = teams_api.team_create(team.as_dict())
return wmodels.Team.from_db_model(result)
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.Team, int, body=wmodels.Team)
def put(self, team_id, team):
@ -203,6 +212,7 @@ class TeamsController(rest.RestController):
# Use default routing for all other requests
return super(TeamsController, self)._route(args, request)
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(None, int)
def delete(self, team_id):

View File

@ -24,6 +24,7 @@ import wsmeext.pecan as wsme_pecan
from storyboard.api.auth import authorization_checks as checks
from storyboard.api.v1.search import search_engine
from storyboard.api.v1 import wmodels
from storyboard.common import decorators
from storyboard.common import event_types
from storyboard.db.api import comments as comments_api
from storyboard.db.api import timeline_events as events_api
@ -37,6 +38,7 @@ SEARCH_ENGINE = search_engine.get_engine()
class TimeLineEventsController(rest.RestController):
"""Manages comments."""
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.TimeLineEvent, int, int)
def get_one(self, story_id, event_id):
@ -57,6 +59,7 @@ class TimeLineEventsController(rest.RestController):
raise ClientSideError(_("Comment %s not found") % event_id,
status_code=404)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.TimeLineEvent], int, int, int, unicode,
unicode)
@ -99,6 +102,7 @@ class TimeLineEventsController(rest.RestController):
class CommentsController(rest.RestController):
"""Manages comments."""
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.Comment, int, int)
def get_one(self, story_id, comment_id):
@ -117,6 +121,7 @@ class CommentsController(rest.RestController):
raise ClientSideError(_("Comment %s not found") % comment_id,
status_code=404)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Comment], int, int, int, unicode, unicode)
def get_all(self, story_id=None, marker=None, limit=None, sort_field='id',
@ -166,6 +171,7 @@ class CommentsController(rest.RestController):
return [wmodels.Comment.from_db_model(comment) for comment in comments]
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.TimeLineEvent, int, body=wmodels.Comment)
def post(self, story_id, comment):
@ -188,6 +194,7 @@ class CommentsController(rest.RestController):
event = wmodels.TimeLineEvent.resolve_event_values(event)
return event
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.Comment, int, int, body=wmodels.Comment)
def put(self, story_id, comment_id, comment_body):
@ -210,6 +217,7 @@ class CommentsController(rest.RestController):
return wmodels.Comment.from_db_model(updated_comment)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.Comment, int, int)
def delete(self, story_id, comment_id):
@ -230,6 +238,7 @@ class CommentsController(rest.RestController):
response.status_code = 204
return response
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Comment], unicode, unicode, int, int)
def search(self, q="", marker=None, limit=None):

View File

@ -24,6 +24,7 @@ import wsmeext.pecan as wsme_pecan
from storyboard.api.auth import authorization_checks as checks
from storyboard.api.v1 import validations
from storyboard.common import decorators
import storyboard.db.api.users as user_api
from storyboard.openstack.common.gettextutils import _ # noqa
@ -35,6 +36,7 @@ LOG = log.getLogger(__name__)
class UserPreferencesController(rest.RestController):
validation_post_schema = validations.USER_PREFERENCES_POST_SCHEMA
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(types.DictType(unicode, unicode), int)
def get_all(self, user_id):
@ -46,6 +48,7 @@ class UserPreferencesController(rest.RestController):
return user_api.user_get_preferences(user_id)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(types.DictType(unicode, unicode), int,
body=types.DictType(unicode, unicode))

View File

@ -27,6 +27,7 @@ import wsmeext.pecan as wsme_pecan
from storyboard.api.auth import authorization_checks as checks
import storyboard.api.v1.wmodels as wmodels
from storyboard.common import decorators
import storyboard.db.api.access_tokens as token_api
import storyboard.db.api.users as user_api
from storyboard.openstack.common.gettextutils import _ # noqa
@ -37,6 +38,7 @@ LOG = log.getLogger(__name__)
class UserTokensController(rest.RestController):
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose([wmodels.AccessToken], int, int, int, unicode,
unicode)
@ -77,6 +79,7 @@ class UserTokensController(rest.RestController):
return [wmodels.AccessToken.from_db_model(t) for t in tokens]
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.AccessToken, int, int)
def get(self, user_id, access_token_id):
@ -94,6 +97,7 @@ class UserTokensController(rest.RestController):
return wmodels.AccessToken.from_db_model(access_token)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.AccessToken, int, body=wmodels.AccessToken)
def post(self, user_id, body):
@ -118,6 +122,7 @@ class UserTokensController(rest.RestController):
return wmodels.AccessToken.from_db_model(token)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.AccessToken, int, int,
body=wmodels.AccessToken)
@ -145,6 +150,7 @@ class UserTokensController(rest.RestController):
return wmodels.AccessToken.from_db_model(result_token)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.AccessToken, int, int)
def delete(self, user_id, access_token_id):

View File

@ -29,6 +29,7 @@ from storyboard.api.v1.user_preferences import UserPreferencesController
from storyboard.api.v1.user_tokens import UserTokensController
from storyboard.api.v1 import validations
from storyboard.api.v1 import wmodels
from storyboard.common import decorators
from storyboard.db.api import users as users_api
from storyboard.openstack.common.gettextutils import _ # noqa
@ -52,6 +53,7 @@ class UsersController(rest.RestController):
validation_post_schema = validations.USERS_POST_SCHEMA
validation_put_schema = validations.USERS_PUT_SCHEMA
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.User], int, int, unicode, unicode, unicode,
unicode)
@ -91,6 +93,7 @@ class UsersController(rest.RestController):
return [wmodels.User.from_db_model(u) for u in users]
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.User, int)
def get_one(self, user_id):
@ -109,6 +112,7 @@ class UsersController(rest.RestController):
status_code=404)
return user
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.User, body=wmodels.User)
def post(self, user):
@ -120,6 +124,7 @@ class UsersController(rest.RestController):
created_user = users_api.user_create(user.as_dict())
return wmodels.User.from_db_model(created_user)
@decorators.db_exceptions
@secure(checks.authenticated)
@wsme_pecan.wsexpose(wmodels.User, int, body=wmodels.User)
def put(self, user_id, user):
@ -151,6 +156,7 @@ class UsersController(rest.RestController):
updated_user = users_api.user_update(user_id, user_dict)
return wmodels.User.from_db_model(updated_user)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.User], unicode, int, int)
def search(self, q="", marker=None, limit=None):

View File

@ -0,0 +1,30 @@
# Copyright (c) 2015 Mirantis Inc.
#
# 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 functools
from pecan import abort
from storyboard.common import exception as exc
def db_exceptions(func):
@functools.wraps(func)
def decorate(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except exc.DBException as db_exc:
abort(db_exc.code, db_exc.message)
return decorate

View File

@ -4,7 +4,7 @@
# 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
# 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,
@ -13,10 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from wsme.exc import ClientSideError
from storyboard.openstack.common.gettextutils import _ # noqa
class StoryboardException(Exception):
class StoryboardException(ClientSideError):
"""Base Exception for the project
To correctly use this class, inherit from it and define
@ -28,8 +30,9 @@ class StoryboardException(Exception):
def __str__(self):
return self.message
def __init__(self):
super(StoryboardException, self).__init__(self.message)
def __init__(self, message=None, status_code=None):
super(StoryboardException, self).__init__(msg=message,
status_code=status_code)
class NotFound(StoryboardException):
@ -40,17 +43,194 @@ class NotFound(StoryboardException):
self.message = message
class DuplicateEntry(StoryboardException):
message = _("Database object already exists")
def __init__(self, message=None):
if message:
self.message = message
class NotEmpty(StoryboardException):
message = _("Database object must be empty")
def __init__(self, message=None):
if message:
self.message = message
class DBException(StoryboardException):
# Base exception for database errors
message = _("Database Exception")
def __init__(self, message=None, status_code=None):
"""Constructor for base exception class
:param: message: exception message.
:param: status_code: code of exception.
"""
if not status_code:
status_code = 400
super(DBException, self).__init__(message=message,
status_code=status_code)
class DBDuplicateEntry(DBException):
"""Duplicate entry exception
This exception wraps the same exception from database.
"""
message = _("Database object already exists.")
def __init__(self, message=None, object_name=None, value=None,
status_code=None):
"""Constructor for duplicate entry exception
:param : message: This message will be shown after exception raised.
:param : object_name: This parameter is name of object, in which
exception was raised.
:param: value: Invalid value.
:param: status_code: code of exception.
If object_name or value is not 'None', to message will be appended with
new message with information about object name or invalid value
"""
super(DBDuplicateEntry, self).__init__(message=message,
status_code=status_code)
db_message = None
if object_name or value:
db_message_list = [_("Database object")]
if object_name:
db_message_list.append(_("\'%s\'") % object_name)
if value:
db_message_list.append(_("with field value \'%s\'") % value)
else:
db_message_list.append(_("with some of unique fields"))
db_message_list.append(_("already exists."))
db_message = _(" ").join(db_message_list)
if db_message:
message_list = []
if message:
message_list.append(message)
message_list.append(db_message)
self.msg = " ".join(message_list)
class DBConnectionError(DBException):
"""Connection error exception
This exception wraps the same exception from database.
"""
message = _("Connection to database failed.")
class ColumnError(DBException):
"""Column error exception
This exception wraps the same exception from database.
"""
message = _("Column is invalid or not found")
class DBDeadLock(DBException):
"""Deadlock exception
This exception wraps the same exception from database.
"""
message = _("Database in dead lock")
class DBInvalidUnicodeParameter(DBException):
"""Invalid unicode parameter exception
This exception wraps the same exception from database.
"""
message = _("Unicode parameter is passed to "
"a database without encoding directive")
class DBMigrationError(DBException):
"""Migration error exception
This exception wraps the same exception from database.
"""
message = _("migrations could not be completed successfully")
class DBReferenceError(DBException):
"""Reference error exception
This exception wraps the same exception from database.
"""
message = _("Foreign key error.")
def __init__(self, message=None, object_name=None, value=None,
key=None, status_code=None):
"""Constructor for duplicate entry exception
:param : message: This message will be shown after exception raised.
:param : object_name: This parameter is name of object, in which
exception was raised.
:param: value: Invalid value.
:param : key: Field with invalid value.
:param : status_code: code of exception.
If object_name or value or key is not 'None', to message will be
appended with new message with information about object name or
invalid value or field with invalid value.
"""
super(DBReferenceError, self).__init__(message=message,
status_code=status_code)
db_message = None
if object_name or value or key:
db_message_list = []
if object_name:
db_message_list.append(
_("Error in object"))
db_message_list.append(_("\'%s\'.") % object_name)
if value or key:
db_message_list.append(_("Field"))
if key:
db_message_list.append(_("\'%s\'") % key)
if value:
db_message_list.append(_("value"))
db_message_list.append(_("\'%s\'") % value)
db_message_list.append(_("is invalid."))
db_message = " ".join(db_message_list)
if db_message:
message_list = []
if message:
message_list.append(message)
else:
message_list.append(self.message)
message_list.append(db_message)
self.msg = " ".join(message_list)
class DBInvalidSortKey(DBException):
"""Invalid sortkey error exception
This exception wraps the same exception from database.
"""
message = _("Invalid sort field")

View File

@ -18,6 +18,7 @@ import datetime
from oslo.db.sqlalchemy.utils import InvalidSortKey
from wsme.exc import ClientSideError
from storyboard.common import exception as exc
from storyboard.db.api import base as api_base
from storyboard.db import models
from storyboard.openstack.common.gettextutils import _ # noqa
@ -56,8 +57,8 @@ def access_token_get_all(marker=None, limit=None, sort_field=None,
marker=marker,
sort_dir=sort_dir)
except InvalidSortKey:
raise ClientSideError(_("Invalid sort_field [%s]") % (sort_field,),
status_code=400)
raise exc.DBInvalidSortKey(
_("Invalid sort_field [%s]") % (sort_field,))
except ValueError as ve:
raise ClientSideError(_("%s") % (ve,), status_code=400)

View File

@ -40,8 +40,15 @@ def _get_facade_instance():
"""Generate an instance of the DB Facade.
"""
global _FACADE
if _FACADE is None:
_FACADE = db_session.EngineFacade.from_config(CONF)
try:
if _FACADE is None:
_FACADE = db_session.EngineFacade.from_config(CONF)
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
return _FACADE
@ -77,21 +84,37 @@ def get_engine():
"""Returns the global instance of our database engine.
"""
facade = _get_facade_instance()
return facade.get_engine(use_slave=True)
try:
return facade.get_engine(use_slave=True)
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
def get_session(autocommit=True, expire_on_commit=False, **kwargs):
"""Returns a database session from our facade.
"""
facade = _get_facade_instance()
return facade.get_session(autocommit=autocommit,
expire_on_commit=expire_on_commit, **kwargs)
try:
return facade.get_session(autocommit=autocommit,
expire_on_commit=expire_on_commit, **kwargs)
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
def cleanup():
"""Manually clean up our database engine.
"""
_destroy_facade_instance()
try:
_destroy_facade_instance()
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
def model_query(model, session=None):
@ -100,13 +123,32 @@ def model_query(model, session=None):
:param model: base model to query
"""
session = session or get_session()
query = session.query(model)
try:
query = session.query(model)
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.ColumnError:
raise exc.ColumnError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
except db_exc.DBInvalidUnicodeParameter:
raise exc.DBInvalidUnicodeParameter()
return query
def __entity_get(kls, entity_id, session):
query = model_query(kls, session)
return query.filter_by(id=entity_id).first()
try:
query = model_query(kls, session)
return query.filter_by(id=entity_id).first()
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.ColumnError:
raise exc.ColumnError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
except db_exc.DBInvalidUnicodeParameter:
raise exc.DBInvalidUnicodeParameter()
def entity_get(kls, entity_id, filter_non_public=False, session=None):
@ -123,7 +165,6 @@ def entity_get(kls, entity_id, filter_non_public=False, session=None):
def entity_get_all(kls, filter_non_public=False, marker=None, limit=None,
sort_field='id', sort_dir='asc', **kwargs):
# Sanity checks, in case someone accidentally explicitly passes in 'None'
if not sort_field:
sort_field = 'id'
@ -144,14 +185,21 @@ def entity_get_all(kls, filter_non_public=False, marker=None, limit=None,
sort_keys=[sort_field],
marker=marker,
sort_dir=sort_dir)
# Execute the query
entities = query.all()
except InvalidSortKey:
raise ClientSideError(_("Invalid sort_field [%s]") % (sort_field,),
status_code=400)
raise exc.DBInvalidSortKey(_("Invalid sort_field [%s]") %
(sort_field,))
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
except db_exc.DBInvalidUnicodeParameter:
raise exc.DBInvalidUnicodeParameter()
except ValueError as ve:
raise ClientSideError(_("%s") % (ve,), status_code=400)
# Execute the query
entities = query.all()
if len(entities) > 0 and filter_non_public:
sample_entity = entities[0] if len(entities) > 0 else None
public_fields = getattr(sample_entity, "_public_fields", [])
@ -169,13 +217,21 @@ def entity_get_count(kls, **kwargs):
# Sanity check on input parameters
query = apply_query_filters(query=query, model=kls, **kwargs)
count = query.count()
try:
count = query.count()
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
except db_exc.DBInvalidUnicodeParameter:
raise exc.DBInvalidUnicodeParameter()
return count
def _filter_non_public_fields(entity, public_list=list()):
ent_copy = copy.copy(entity)
for attr_name, val in six.iteritems(entity.__dict__):
if attr_name.startswith("_"):
continue
@ -191,12 +247,25 @@ def entity_create(kls, values):
entity.update(values.copy())
session = get_session()
with session.begin():
try:
try:
with session.begin():
session.add(entity)
except db_exc.DBDuplicateEntry:
raise exc.DuplicateEntry(_("Duplicate entry for : %s")
% kls.__name__)
except db_exc.DBDuplicateEntry as de:
raise exc.DBDuplicateEntry(object_name=kls.__name__,
value=de.value)
except db_exc.DBReferenceError as re:
raise exc.DBReferenceError(object_name=kls.__name__,
value=re.constraint, key=re.key)
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.ColumnError:
raise exc.ColumnError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
except db_exc.DBInvalidUnicodeParameter:
raise exc.DBInvalidUnicodeParameter
return entity
@ -204,16 +273,32 @@ def entity_create(kls, values):
def entity_update(kls, entity_id, values):
session = get_session()
with session.begin():
entity = __entity_get(kls, entity_id, session)
if entity is None:
raise exc.NotFound(_("%(name)s %(id)s not found") %
{'name': kls.__name__, 'id': entity_id})
try:
with session.begin():
entity = __entity_get(kls, entity_id, session)
if entity is None:
raise exc.NotFound(_("%(name)s %(id)s not found") %
{'name': kls.__name__, 'id': entity_id})
values_copy = values.copy()
values_copy["id"] = entity_id
entity.update(values_copy)
session.add(entity)
values_copy = values.copy()
values_copy["id"] = entity_id
entity.update(values_copy)
session.add(entity)
except db_exc.DBDuplicateEntry as de:
raise exc.DBDuplicateEntry(object_name=kls.__name__,
value=de.value)
except db_exc.DBReferenceError as re:
raise exc.DBReferenceError(object_name=kls.__name__,
value=re.constraint, key=re.key)
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.ColumnError:
raise exc.ColumnError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
except db_exc.DBInvalidUnicodeParameter:
raise exc.DBInvalidUnicodeParameter
session = get_session()
entity = __entity_get(kls, entity_id, session)
@ -223,11 +308,25 @@ def entity_update(kls, entity_id, values):
def entity_hard_delete(kls, entity_id):
session = get_session()
with session.begin():
query = model_query(kls, session)
entity = query.filter_by(id=entity_id).first()
if entity is None:
raise exc.NotFound(_("%(name)s %(id)s not found") %
{'name': kls.__name__, 'id': entity_id})
session.delete(entity)
try:
with session.begin():
query = model_query(kls, session)
entity = query.filter_by(id=entity_id).first()
if entity is None:
raise exc.NotFound(_("%(name)s %(id)s not found") %
{'name': kls.__name__, 'id': entity_id})
session.delete(entity)
except db_exc.DBReferenceError as re:
raise exc.DBReferenceError(object_name=kls.__name__,
value=re.constraint, key=re.key)
except db_exc.DBConnectionError:
raise exc.DBConnectionError()
except db_exc.ColumnError:
raise exc.ColumnError()
except db_exc.DBDeadlock:
raise exc.DBDeadLock()
except db_exc.DBInvalidUnicodeParameter:
raise exc.DBInvalidUnicodeParameter()

View File

@ -4,7 +4,7 @@
# 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
# 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,
@ -16,6 +16,7 @@
from oslo.db.sqlalchemy.utils import InvalidSortKey
from wsme.exc import ClientSideError
from storyboard.common import exception as exc
from storyboard.db.api import base as api_base
from storyboard.db import models
from storyboard.openstack.common.gettextutils import _ # noqa
@ -50,8 +51,8 @@ def project_get_all(marker=None, limit=None, sort_field=None, sort_dir=None,
marker=marker,
sort_dir=sort_dir)
except InvalidSortKey:
raise ClientSideError(_("Invalid sort_field [%s]") % (sort_field,),
status_code=400)
raise exc.DBInvalidSortKey(
_("Invalid sort_field [%s]") % (sort_field,))
except ValueError as ve:
raise ClientSideError(_("%s") % (ve,), status_code=400)

View File

@ -91,8 +91,8 @@ def story_get_all(title=None, description=None, status=None, assignee_id=None,
marker=marker,
sort_dir=sort_dir)
except InvalidSortKey:
raise ClientSideError(_("Invalid sort_field [%s]") % (sort_field,),
status_code=400)
raise exc.DBInvalidSortKey(
_("Invalid sort_field [%s]") % (sort_field,))
except ValueError as ve:
raise ClientSideError(_("%s") % (ve,), status_code=400)

View File

@ -4,7 +4,7 @@
# 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
# 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,
@ -16,6 +16,7 @@
from oslo.db.sqlalchemy.utils import InvalidSortKey
from wsme.exc import ClientSideError
from storyboard.common import exception as exc
from storyboard.db.api import base as api_base
from storyboard.db import models
from storyboard.openstack.common.gettextutils import _ # noqa
@ -44,8 +45,8 @@ def task_get_all(marker=None, limit=None, sort_field=None, sort_dir=None,
marker=marker,
sort_dir=sort_dir)
except InvalidSortKey:
raise ClientSideError(_("Invalid sort_field [%s]") % (sort_field,),
status_code=400)
raise exc.DBInvalidSortKey(
_("Invalid sort_field [%s]") % (sort_field,))
except ValueError as ve:
raise ClientSideError("%s" % (ve,), status_code=400)

View File

@ -73,8 +73,8 @@ def user_update_preferences(user_id, preferences):
for key in preferences:
value = preferences[key]
prefs = api_base.entity_get_all(models.UserPreference,
user_id=user_id,
key=key)
user_id=user_id,
key=key)
if prefs:
pref = prefs[0]

View File

@ -0,0 +1,65 @@
# Copyright (c) 2015 Mirantis Inc.
#
# 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
from storyboard.tests import base
class TestDBExceptions(base.FunctionalTest):
def setUp(self):
super(TestDBExceptions, self).setUp()
self.default_headers['Authorization'] = 'Bearer valid_superuser_token'
# test duplicate entry error
# in this test we try to create two equal projects
def test_duplicate_project_create(self):
resource = '/projects'
project = {
'name': 'test-project-duplicate',
'description': 'test_project_duplicate_description',
}
# create project with name 'test-project-duplicate'
response = self.post_json(resource, project)
body = json.loads(response.body)
self.assertEqual(project['name'], body['name'])
self.assertEqual(project['description'], body['description'])
# repeat creating this project
# because project with name 'test-project-duplicate' already exists, we
# wait abort with code_status 400
response = self.post_json(resource, project, expect_errors=True)
self.assertEqual(400, response.status_code)
# test duplicate entry error
# in this test we try to create two equal users
def test_duplicate_user_create(self):
# send user first time
resource = '/users'
user = {
'username': 'test_duplicate',
'full_name': 'Test duplicate',
'email': 'dupe@example.com'
}
response = self.post_json(resource, user)
users_body = json.loads(response.body)
self.assertEqual(user['username'], users_body['username'])
self.assertEqual(user['full_name'], users_body['full_name'])
self.assertEqual(user['email'], users_body['email'])
# send user again
response = self.post_json(resource, user, expect_errors=True)
self.assertEqual(400, response.status_code)

View File

@ -0,0 +1,77 @@
# Copyright (c) 2015 Mirantis Inc.
#
# 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 six
from storyboard.common import exception as exc
from storyboard.db.api import projects
from storyboard.db.api import tasks
from storyboard.tests import base
class TestDBDuplicateEntry(base.DbTestCase):
def setUp(self):
super(TestDBDuplicateEntry, self).setUp()
# create two projects with equal names
def test_users(self):
project = {
'id': 10,
'name': 'project',
'description': 'Project 4 Description - foo'
}
projects.project_create(project)
self.assertRaises(exc.DBDuplicateEntry,
lambda: projects.project_create(project))
class TestDBReferenceError(base.DbTestCase):
def setUp(self):
super(TestDBReferenceError, self).setUp()
# create task with id of not existing story
def test_teams(self):
task = {
'id': 10,
'story_id': 100
}
self.assertRaises(exc.DBReferenceError,
lambda: tasks.task_create(task))
class TestDbInvalidSortKey(base.DbTestCase):
def setUp(self):
super(TestDbInvalidSortKey, self).setUp()
# create project and sort his field with incorrect key
def test_projects(self):
project = {
'id': 10,
'name': 'testProject',
'description': 'testProjectDescription'
}
saved_project = projects.project_create(project)
self.assertIsNotNone(saved_project)
for k, v in six.iteritems(project):
self.assertEqual(saved_project[k], v)
self.assertRaises(exc.DBInvalidSortKey,
lambda: projects.project_get_all(
marker=10,
sort_field='invalid_sort_field'))