zaqar/marconi/proxy/transport/wsgi/queues.py
kgriffs c67d5a4a25 fix(queues): Global config used everywhere
This patch modifies the queues code to use manually instantiated configs
rather than the global config. This will allow for more flexibility in
configuring components. For example, the up-and-coming sharding feature
can construct configs based on the shard catalog rather than the local
INI file. Also, this change will make testing easier.

Also in this patch, the SQLite driver schema was consolidated into
one place in preparation for the sharding patch. With this change,
controllers can be instantiated and used independently, since all
the tables are set up when the driver is loaded, not in a piecemeal
fashion as before.

Change-Id: I1afa8ab8c7e6dd9a017f4a9e3a3b1fadbeb32806
Implements: blueprint remove-global-config
Closes-Bug: #1239725
2013-10-16 16:09:28 -05:00

192 lines
7.0 KiB
Python

# Copyright (c) 2013 Rackspace Hosting, 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.
"""queues: routing and cataloguing queue operations on marconi
The queues resource performs routing to a marconi partition for
requests targeting queues.
For the case of a queue listing, the proxy handles the request in its
entirety, since queues for a given project may be spread across
multiple partitions. This requires the proxy catalogue being
consistent with the state of the entire deployment.
For the case of accessing a particular queue, the catalogue is updated
based on the operation. A DELETE removes entries from the catalogue. A
PUT adds an entry to the catalogue. A GET asks marconi for an
authoritative response.
"""
import collections
import json
import falcon
from oslo.config import cfg
import six
from marconi.openstack.common import log
from marconi.proxy.utils import (
forward, lookup, helpers, http, partition
)
from marconi.queues.storage import base as storage
from marconi.queues.transport import validation as validate
from marconi.queues.transport.wsgi import exceptions as wsgi_exceptions
LOG = log.getLogger(__name__)
CFG = cfg.CONF
CFG.register_opts(storage._LIMITS_OPTIONS, group=storage._LIMITS_GROUP)
STORAGE_LIMITS = cfg.CONF[storage._LIMITS_GROUP]
class Listing(object):
"""Responsible for constructing a valid marconi queue listing
from the content stored in the catalogue
:param catalogue_controller: storage driver to use
"""
def __init__(self, catalogue_controller):
self._catalogue = catalogue_controller
#TODO(cpp-cabrera): consider revisiting this implementation
# to use concurrent requests + merge/sort
# for great impl./data DRYness
def on_get(self, request, response):
project = helpers.get_project(request)
LOG.debug('LIST queues - project: {0}'.format(project))
kwargs = {}
request.get_param('marker', store=kwargs)
request.get_param_as_int('limit', store=kwargs)
request.get_param_as_bool('detailed', store=kwargs)
resp = collections.defaultdict(list)
limit = kwargs.get('limit',
STORAGE_LIMITS.default_queue_paging)
try:
validate.queue_listing(limit=limit)
except validate.ValidationFailed as ex:
raise wsgi_exceptions.HTTPBadRequestAPI(six.text_type(ex))
for queue in self._catalogue.list(project):
queue_name = queue['name']
if queue_name < kwargs.get('marker', ''):
continue
entry = {
'href': request.path + '/' + queue_name,
'name': queue_name
}
if kwargs.get('detailed', None):
entry['metadata'] = queue['metadata']
resp['queues'].append(entry)
kwargs['marker'] = queue_name
if len(resp['queues']) == limit:
break
if not resp:
LOG.debug('LIST queues - no queues found')
response.status = falcon.HTTP_204
return
resp['links'].append({
'rel': 'next',
'href': request.path + falcon.to_query_str(kwargs)
})
response.content_location = request.relative_uri
response.body = json.dumps(resp, ensure_ascii=False)
class Resource(forward.ForwardMixin):
"""Forwards queue requests to marconi queues API and updates the
catalogue
:param partitions_controller: partitions storage driver
:param catalogue_conroller: catalogue storage driver
:param cache: caching driver
:param selector: algorithm to use to select next node
:param methods: HTTP methods to automatically derive from mixin
"""
def __init__(self, partitions_controller, catalogue_controller,
cache, selector):
self._partitions = partitions_controller
self._catalogue = catalogue_controller
self._cache = cache
super(Resource, self).__init__(partitions_controller,
catalogue_controller,
cache, selector,
methods=['get', 'head'])
def on_put(self, request, response, queue):
"""Create a queue in the catalogue, then forwards to marconi.
This is the only time marconi proxy ever needs to select a
partition for a queue. The association is created in the
catalogue. This should also be the only time
partition.weighted_select is ever called.
:raises: HTTPInternalServerError - if no partitions are registered
"""
project = helpers.get_project(request)
# NOTE(cpp-cabrera): if we've already registered a queue,
# don't try to create it again, because it will duplicate it
# across partitions.
#
# There exists a race condition here, but it is benign. It's
# possible that after the existence check has succeeded,
# another request may succeed in DELETEing a queue. In this
# scenario, the queue will be recreated on another partition,
# which is reasonable, since the user meant to both DELETE and
# PUT That queue.
if lookup.exists(project, queue,
self._catalogue, self._cache):
response.status = falcon.HTTP_204
return
target = partition.weighted_select(self._partitions.list())
if target is None:
LOG.error('No partitions registered')
raise falcon.HTTPInternalServerError(
"No partitions registered",
"Contact the system administrator for more details."
)
host = target['hosts'][0]
resp = helpers.forward(host, request)
# NOTE(cpp-cabrera): only catalogue a queue if it was created
if resp.status_code == 201:
self._catalogue.insert(project, queue, target['name'],
host)
response.set_headers(helpers.capitalized(resp.headers))
response.status = http.status(resp.status_code)
response.body = resp.content
def on_delete(self, request, response, queue):
project = helpers.get_project(request)
LOG.debug('DELETE queue - project/queue: {0}/{1}'.format(
project, queue
))
resp = self.forward(request, response, queue)
# avoid deleting a queue if the request is bad
if resp.ok:
self._catalogue.delete(project, queue)
lookup.invalidate_entry(project, queue, self._cache)