Files
zaqar/marconi/transport/wsgi/messages.py
Flaper Fesp a30092c55e Use queue's name and project to get messages
Mongodb's driver currently queries the queue's collection to get queue
id and then filter messages with it. Instead of storing the id, this
patches stores the queue name and the project in the message collection
and use that for querying messages.

It's safe to assume that the queue exists when messages are returned by
the query, however, posts operation will still verify queue's existence.

Fixes bug: #1207018

Change-Id: Iaa7bc3f1300b3349a5cfad5a9f6ecabb2f75e95e
2013-08-08 18:36:42 +02:00

294 lines
9.9 KiB
Python

# Copyright (c) 2013 Rackspace, 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 itertools
import falcon
import marconi.openstack.common.log as logging
from marconi.storage import exceptions as storage_exceptions
from marconi.transport import helpers
from marconi.transport.wsgi import exceptions as wsgi_exceptions
from marconi.transport.wsgi import helpers as wsgi_helpers
LOG = logging.getLogger(__name__)
MESSAGE_POST_SPEC = (('ttl', int), ('body', '*'))
class CollectionResource(object):
__slots__ = ('message_controller')
def __init__(self, message_controller):
self.message_controller = message_controller
#-----------------------------------------------------------------------
# Helpers
#-----------------------------------------------------------------------
def _get_by_id(self, base_path, project_id, queue_name, ids):
"""Returns one or more messages from the queue by ID."""
try:
messages = self.message_controller.bulk_get(
queue_name,
message_ids=ids,
project=project_id)
except Exception as ex:
LOG.exception(ex)
description = _('Message could not be retrieved.')
raise wsgi_exceptions.HTTPServiceUnavailable(description)
# Prepare response
messages = list(messages)
if not messages:
return None
base_path += '/'
for each_message in messages:
each_message['href'] = base_path + each_message['id']
del each_message['id']
return messages
def _get(self, req, project_id, queue_name):
uuid = req.get_header('Client-ID', required=True)
# TODO(kgriffs): Optimize
kwargs = helpers.purge({
'marker': req.get_param('marker'),
'limit': req.get_param_as_int('limit'),
'echo': req.get_param_as_bool('echo'),
})
try:
results = self.message_controller.list(
queue_name,
project=project_id,
client_uuid=uuid,
**kwargs)
# Buffer messages
cursor = next(results)
messages = list(cursor)
except storage_exceptions.DoesNotExist:
raise falcon.HTTPNotFound()
except storage_exceptions.MalformedMarker:
title = _('Invalid query string parameter')
description = _('The value for the query string '
'parameter "marker" could not be '
'parsed. We recommend using the '
'"next" URI from a previous '
'request directly, rather than '
'constructing the URI manually. ')
raise falcon.HTTPBadRequest(title, description)
except Exception as ex:
LOG.exception(ex)
description = _('Messages could not be listed.')
raise wsgi_exceptions.HTTPServiceUnavailable(description)
if not messages:
return None
# Found some messages, so prepare the response
kwargs['marker'] = next(results)
for each_message in messages:
each_message['href'] = req.path + '/' + each_message['id']
del each_message['id']
return {
'messages': messages,
'links': [
{
'rel': 'next',
'href': req.path + falcon.to_query_str(kwargs)
}
]
}
#-----------------------------------------------------------------------
# Interface
#-----------------------------------------------------------------------
def on_post(self, req, resp, project_id, queue_name):
LOG.debug(_("Messages collection POST - queue: %(queue)s, "
"project: %(project)s") %
{"queue": queue_name, "project": project_id})
uuid = req.get_header('Client-ID', required=True)
# Pull out just the fields we care about
messages = wsgi_helpers.filter_stream(
req.stream,
req.content_length,
MESSAGE_POST_SPEC,
doctype=wsgi_helpers.JSONArray)
# Verify that at least one message was provided.
try:
first_message = next(messages)
except StopIteration:
description = _('No messages were provided.')
raise wsgi_exceptions.HTTPBadRequestBody(description)
# Hack to make message_controller oblivious to the
# fact that we just popped the first message.
messages = itertools.chain((first_message,), messages)
# Enqueue the messages
partial = False
try:
message_ids = self.message_controller.post(
queue_name,
messages=messages,
project=project_id,
client_uuid=uuid)
except storage_exceptions.DoesNotExist:
raise falcon.HTTPNotFound()
except storage_exceptions.MessageConflict as ex:
LOG.exception(ex)
partial = True
message_ids = ex.succeeded_ids
if not message_ids:
# TODO(kgriffs): Include error code that is different
# from the code used in the generic case, below.
description = _('No messages could be enqueued.')
raise wsgi_exceptions.HTTPServiceUnavailable(description)
except Exception as ex:
LOG.exception(ex)
description = _('Messages could not be enqueued.')
raise wsgi_exceptions.HTTPServiceUnavailable(description)
# Prepare the response
resp.status = falcon.HTTP_201
ids_value = ','.join(message_ids)
resp.location = req.path + '?ids=' + ids_value
hrefs = [req.path + '/' + id for id in message_ids]
body = {'resources': hrefs, 'partial': partial}
resp.body = helpers.to_json(body)
def on_get(self, req, resp, project_id, queue_name):
LOG.debug(_("Messages collection GET - queue: %(queue)s, "
"project: %(project)s") %
{"queue": queue_name, "project": project_id})
resp.content_location = req.relative_uri
ids = req.get_param_as_list('ids')
if ids is None:
response = self._get(req, project_id, queue_name)
else:
base_path = req.path + '/messages'
response = self._get_by_id(base_path, project_id, queue_name, ids)
if response is None:
resp.status = falcon.HTTP_204
return
resp.body = helpers.to_json(response)
def on_delete(self, req, resp, project_id, queue_name):
# NOTE(zyuan): Attempt to delete the whole message collection
# (without an "ids" parameter) is not allowed
ids = req.get_param_as_list('ids', required=True)
try:
self.message_controller.bulk_delete(
queue_name,
message_ids=ids,
project=project_id)
except Exception as ex:
LOG.exception(ex)
description = 'Messages could not be deleted.'
raise wsgi_exceptions.HTTPServiceUnavailable(description)
else:
resp.status = falcon.HTTP_204
class ItemResource(object):
__slots__ = ('message_controller')
def __init__(self, message_controller):
self.message_controller = message_controller
def on_get(self, req, resp, project_id, queue_name, message_id):
LOG.debug(_("Messages item GET - message: %(message)s, "
"queue: %(queue)s, project: %(project)s") %
{"message": message_id,
"queue": queue_name,
"project": project_id})
try:
message = self.message_controller.get(
queue_name,
message_id,
project=project_id)
except storage_exceptions.DoesNotExist:
raise falcon.HTTPNotFound()
except Exception as ex:
LOG.exception(ex)
description = _('Message could not be retrieved.')
raise wsgi_exceptions.HTTPServiceUnavailable(description)
# Prepare response
message['href'] = req.path
del message['id']
resp.content_location = req.relative_uri
resp.body = helpers.to_json(message)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp, project_id, queue_name, message_id):
LOG.debug(_("Messages item DELETE - message: %(message)s, "
"queue: %(queue)s, project: %(project)s") %
{"message": message_id,
"queue": queue_name,
"project": project_id})
try:
self.message_controller.delete(
queue_name,
message_id=message_id,
project=project_id,
claim=req.get_param('claim_id'))
except storage_exceptions.NotPermitted as ex:
LOG.exception(ex)
title = _('Invalid claim')
description = _('The specified claim either does not '
'exist or has expired.')
raise falcon.HTTPForbidden(title, description)
except Exception as ex:
LOG.exception(ex)
description = _('Message could not be deleted.')
raise wsgi_exceptions.HTTPServiceUnavailable(description)
# Alles guete
resp.status = falcon.HTTP_204