Merge "Implement mongodb driver for notifications"
This commit is contained in:
commit
898ced1ec1
@ -41,7 +41,8 @@ from zaqar.tests.unit.storage import base
|
|||||||
class MongodbSetupMixin(object):
|
class MongodbSetupMixin(object):
|
||||||
def _purge_databases(self):
|
def _purge_databases(self):
|
||||||
databases = (self.driver.message_databases +
|
databases = (self.driver.message_databases +
|
||||||
[self.driver.queues_database])
|
[self.driver.queues_database,
|
||||||
|
self.driver.subscriptions_database])
|
||||||
|
|
||||||
for db in databases:
|
for db in databases:
|
||||||
self.driver.connection.drop_database(db)
|
self.driver.connection.drop_database(db)
|
||||||
@ -444,6 +445,14 @@ class MongodbClaimTests(MongodbSetupMixin, base.ClaimControllerTest):
|
|||||||
project=self.project)
|
project=self.project)
|
||||||
|
|
||||||
|
|
||||||
|
@testing.requires_mongodb
|
||||||
|
class MongodbSubscriptionTests(MongodbSetupMixin,
|
||||||
|
base.SubscriptionControllerTest):
|
||||||
|
driver_class = mongodb.DataDriver
|
||||||
|
config_file = 'wsgi_mongodb.conf'
|
||||||
|
controller_class = controllers.SubscriptionController
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# TODO(kgriffs): Do these need database purges as well as those above?
|
# TODO(kgriffs): Do these need database purges as well as those above?
|
||||||
#
|
#
|
||||||
|
@ -24,11 +24,13 @@ CatalogueBase = base.CatalogueBase
|
|||||||
Claim = base.Claim
|
Claim = base.Claim
|
||||||
Message = base.Message
|
Message = base.Message
|
||||||
Queue = base.Queue
|
Queue = base.Queue
|
||||||
|
Subscription = base.Subscription
|
||||||
PoolsBase = base.PoolsBase
|
PoolsBase = base.PoolsBase
|
||||||
FlavorsBase = base.FlavorsBase
|
FlavorsBase = base.FlavorsBase
|
||||||
|
|
||||||
DEFAULT_QUEUES_PER_PAGE = base.DEFAULT_QUEUES_PER_PAGE
|
DEFAULT_QUEUES_PER_PAGE = base.DEFAULT_QUEUES_PER_PAGE
|
||||||
DEFAULT_MESSAGES_PER_PAGE = base.DEFAULT_MESSAGES_PER_PAGE
|
DEFAULT_MESSAGES_PER_PAGE = base.DEFAULT_MESSAGES_PER_PAGE
|
||||||
DEFAULT_POOLS_PER_PAGE = base.DEFAULT_POOLS_PER_PAGE
|
DEFAULT_POOLS_PER_PAGE = base.DEFAULT_POOLS_PER_PAGE
|
||||||
|
DEFAULT_SUBSCRIPTIONS_PER_PAGE = base.DEFAULT_SUBSCRIPTIONS_PER_PAGE
|
||||||
|
|
||||||
DEFAULT_MESSAGES_PER_CLAIM = base.DEFAULT_MESSAGES_PER_CLAIM
|
DEFAULT_MESSAGES_PER_CLAIM = base.DEFAULT_MESSAGES_PER_CLAIM
|
||||||
|
@ -31,6 +31,7 @@ import zaqar.openstack.common.log as logging
|
|||||||
DEFAULT_QUEUES_PER_PAGE = 10
|
DEFAULT_QUEUES_PER_PAGE = 10
|
||||||
DEFAULT_MESSAGES_PER_PAGE = 10
|
DEFAULT_MESSAGES_PER_PAGE = 10
|
||||||
DEFAULT_POOLS_PER_PAGE = 10
|
DEFAULT_POOLS_PER_PAGE = 10
|
||||||
|
DEFAULT_SUBSCRIPTIONS_PER_PAGE = 10
|
||||||
|
|
||||||
DEFAULT_MESSAGES_PER_CLAIM = 10
|
DEFAULT_MESSAGES_PER_CLAIM = 10
|
||||||
|
|
||||||
@ -223,6 +224,11 @@ class DataDriverBase(DriverBase):
|
|||||||
"""Returns the driver's claim controller."""
|
"""Returns the driver's claim controller."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def subscription_controller(self):
|
||||||
|
"""Returns the driver's subscription controller."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class ControlDriverBase(DriverBase):
|
class ControlDriverBase(DriverBase):
|
||||||
@ -564,6 +570,111 @@ class Claim(ControllerBase):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class Subscription(ControllerBase):
|
||||||
|
"""This class is responsible for managing subscriptions of notification.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def list(self, queue, project=None, marker=None,
|
||||||
|
limit=DEFAULT_SUBSCRIPTIONS_PER_PAGE):
|
||||||
|
"""Base method for listing subscriptions.
|
||||||
|
|
||||||
|
:param queue: Name of the queue to get the subscriptions from.
|
||||||
|
:type queue: six.text_type
|
||||||
|
:param project: Project this subscription belongs to.
|
||||||
|
:type project: six.text_type
|
||||||
|
:param marker: used to determine which subscription to start with
|
||||||
|
:type marker: six.text_type
|
||||||
|
:param limit: (Default 10) Max number of results to return
|
||||||
|
:type limit: int
|
||||||
|
:returns: An iterator giving a sequence of subscriptions
|
||||||
|
and the marker of the next page.
|
||||||
|
:rtype: [{}]
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get(self, queue, subscription_id, project=None):
|
||||||
|
"""Returns a single subscription entry.
|
||||||
|
|
||||||
|
:param queue: Name of the queue subscription belongs to.
|
||||||
|
:type queue: six.text_type
|
||||||
|
:param subscription_id: ID of this subscription
|
||||||
|
:type subscription_id: six.text_type
|
||||||
|
:param project: Project this subscription belongs to.
|
||||||
|
:type project: six.text_type
|
||||||
|
:returns: Dictionary containing subscription data
|
||||||
|
:rtype: {}
|
||||||
|
:raises: SubscriptionDoesNotExist if not found
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def create(self, queue, subscriber, ttl, options, project=None):
|
||||||
|
"""Create a new subscription.
|
||||||
|
|
||||||
|
:param queue:The source queue for notifications
|
||||||
|
:type queue: six.text_type
|
||||||
|
:param subscriber: The subscriber URI
|
||||||
|
:type subscriber: six.text_type
|
||||||
|
:param ttl: time to live for this subscription
|
||||||
|
:type ttl: int
|
||||||
|
:param options: Options used to configure this subscription
|
||||||
|
:type options: dict
|
||||||
|
:param project: Project id
|
||||||
|
:type project: six.text_type
|
||||||
|
:returns: True if a subscription was created and False
|
||||||
|
if it is failed.
|
||||||
|
:rtype: boolean
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def update(self, queue, subscription_id, project=None, **kwargs):
|
||||||
|
"""Updates the weight, uris, and/or options of this subscription
|
||||||
|
|
||||||
|
:param queue: Name of the queue subscription belongs to.
|
||||||
|
:type queue: six.text_type
|
||||||
|
:param name: ID of the subscription
|
||||||
|
:type name: text
|
||||||
|
:param kwargs: one of: `source`, `subscriber`, `ttl`, `options`
|
||||||
|
:type kwargs: dict
|
||||||
|
:raises: SubscriptionDoesNotExist if not found
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def exists(self, queue, subscription_id, project=None):
|
||||||
|
"""Base method for testing subscription existence.
|
||||||
|
|
||||||
|
:param queue: Name of the queue subscription belongs to.
|
||||||
|
:type queue: six.text_type
|
||||||
|
:param subscription_id: ID of subscription
|
||||||
|
:type subscription_id: six.text_type
|
||||||
|
:param project: Project id
|
||||||
|
:type project: six.text_type
|
||||||
|
:returns: True if a subscription exists and False
|
||||||
|
if it does not.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def delete(self, queue, subscription_id, project=None):
|
||||||
|
"""Base method for deleting a subscription.
|
||||||
|
|
||||||
|
:param queue: Name of the queue subscription belongs to.
|
||||||
|
:type queue: six.text_type
|
||||||
|
:param subscription_id: ID of the subscription to be deleted.
|
||||||
|
:type subscription_id: six.text_type
|
||||||
|
:param project: Project id
|
||||||
|
:type project: six.text_type
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class PoolsBase(ControllerBase):
|
class PoolsBase(ControllerBase):
|
||||||
"""A controller for managing pools."""
|
"""A controller for managing pools."""
|
||||||
|
@ -176,3 +176,12 @@ class PoolInUseByFlavor(NotPermitted):
|
|||||||
@property
|
@property
|
||||||
def flavor(self):
|
def flavor(self):
|
||||||
return self._flavor
|
return self._flavor
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionDoesNotExist(DoesNotExist):
|
||||||
|
|
||||||
|
msg_format = u'Subscription {subscription_id} does not exist'
|
||||||
|
|
||||||
|
def __init__(self, subscription_id):
|
||||||
|
super(SubscriptionDoesNotExist,
|
||||||
|
self).__init__(subscription_id=subscription_id)
|
||||||
|
@ -28,6 +28,7 @@ from zaqar.storage.mongodb import flavors
|
|||||||
from zaqar.storage.mongodb import messages
|
from zaqar.storage.mongodb import messages
|
||||||
from zaqar.storage.mongodb import pools
|
from zaqar.storage.mongodb import pools
|
||||||
from zaqar.storage.mongodb import queues
|
from zaqar.storage.mongodb import queues
|
||||||
|
from zaqar.storage.mongodb import subscriptions
|
||||||
|
|
||||||
|
|
||||||
CatalogueController = catalogue.CatalogueController
|
CatalogueController = catalogue.CatalogueController
|
||||||
@ -36,3 +37,4 @@ FlavorsController = flavors.FlavorsController
|
|||||||
MessageController = messages.MessageController
|
MessageController = messages.MessageController
|
||||||
QueueController = queues.QueueController
|
QueueController = queues.QueueController
|
||||||
PoolsController = pools.PoolsController
|
PoolsController = pools.PoolsController
|
||||||
|
SubscriptionController = subscriptions.SubscriptionController
|
||||||
|
@ -165,6 +165,12 @@ class DataDriver(storage.DataDriverBase):
|
|||||||
return [self.connection[name + '_messages_p' + str(p)]
|
return [self.connection[name + '_messages_p' + str(p)]
|
||||||
for p in range(partitions)]
|
for p in range(partitions)]
|
||||||
|
|
||||||
|
@decorators.lazy_property(write=False)
|
||||||
|
def subscriptions_database(self):
|
||||||
|
"""Database dedicated to the "subscription" collection."""
|
||||||
|
name = self.mongodb_conf.database + '_subscriptions'
|
||||||
|
return self.connection[name]
|
||||||
|
|
||||||
@decorators.lazy_property(write=False)
|
@decorators.lazy_property(write=False)
|
||||||
def connection(self):
|
def connection(self):
|
||||||
"""MongoDB client connection instance."""
|
"""MongoDB client connection instance."""
|
||||||
@ -182,6 +188,10 @@ class DataDriver(storage.DataDriverBase):
|
|||||||
def claim_controller(self):
|
def claim_controller(self):
|
||||||
return controllers.ClaimController(self)
|
return controllers.ClaimController(self)
|
||||||
|
|
||||||
|
@decorators.lazy_property(write=False)
|
||||||
|
def subscription_controller(self):
|
||||||
|
return controllers.SubscriptionController(self)
|
||||||
|
|
||||||
|
|
||||||
class ControlDriver(storage.ControlDriverBase):
|
class ControlDriver(storage.ControlDriverBase):
|
||||||
|
|
||||||
|
151
zaqar/storage/mongodb/subscriptions.py
Normal file
151
zaqar/storage/mongodb/subscriptions.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# Copyright (c) 2014 Catalyst IT Ltd.
|
||||||
|
#
|
||||||
|
# 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 oslo.utils import timeutils
|
||||||
|
import pymongo.errors
|
||||||
|
|
||||||
|
from zaqar.common import utils as common_utils
|
||||||
|
from zaqar.storage import base
|
||||||
|
from zaqar.storage import errors
|
||||||
|
from zaqar.storage.mongodb import utils
|
||||||
|
|
||||||
|
ID_INDEX_FIELDS = [('_id', 1)]
|
||||||
|
|
||||||
|
SUBSCRIPTIONS_INDEX = [
|
||||||
|
('s', 1),
|
||||||
|
('u', 1),
|
||||||
|
('p', 1),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionController(base.Subscription):
|
||||||
|
"""Implements subscription resource operations using MongoDB.
|
||||||
|
|
||||||
|
Subscriptions are unique by project + queue/topic + subscriber.
|
||||||
|
|
||||||
|
Schema:
|
||||||
|
's': source :: six.text_type
|
||||||
|
'u': subscriber:: six.text_type
|
||||||
|
't': ttl:: int
|
||||||
|
'e': expires: int
|
||||||
|
'o': options :: dict
|
||||||
|
'p': project :: six.text_type
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(SubscriptionController, self).__init__(*args, **kwargs)
|
||||||
|
self._collection = self.driver.subscriptions_database.subscriptions
|
||||||
|
self._queue_collection = self.driver.queues_database.queues
|
||||||
|
self._collection.ensure_index(SUBSCRIPTIONS_INDEX, unique=True)
|
||||||
|
|
||||||
|
@utils.raises_conn_error
|
||||||
|
def list(self, queue, project=None, marker=None, limit=10):
|
||||||
|
query = {'s': queue, 'p': project}
|
||||||
|
if marker is not None:
|
||||||
|
query['_id'] = {'$gt': marker}
|
||||||
|
|
||||||
|
fields = {'s': 1, 'u': 1, 't': 1, 'p': 1, 'o': 1, '_id': 1}
|
||||||
|
|
||||||
|
cursor = self._collection.find(query, fields=fields)
|
||||||
|
cursor = cursor.limit(limit).sort('_id')
|
||||||
|
marker_name = {}
|
||||||
|
|
||||||
|
def normalizer(record):
|
||||||
|
ret = {
|
||||||
|
'id': str(record['_id']),
|
||||||
|
'source': record['s'],
|
||||||
|
'subscriber': record['u'],
|
||||||
|
'ttl': record['t'],
|
||||||
|
'options': record['o'],
|
||||||
|
}
|
||||||
|
marker_name['next'] = record['_id']
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
yield utils.HookedCursor(cursor, normalizer)
|
||||||
|
yield marker_name and marker_name['next']
|
||||||
|
|
||||||
|
@utils.raises_conn_error
|
||||||
|
def get(self, queue, subscription_id, project=None):
|
||||||
|
res = self._collection.find_one({'_id': utils.to_oid(subscription_id),
|
||||||
|
'p': project})
|
||||||
|
|
||||||
|
if not res:
|
||||||
|
raise errors.SubscriptionDoesNotExist(subscription_id)
|
||||||
|
|
||||||
|
return _normalize(res)
|
||||||
|
|
||||||
|
@utils.raises_conn_error
|
||||||
|
def create(self, queue, subscriber, ttl, options, project=None):
|
||||||
|
source = queue
|
||||||
|
now = timeutils.utcnow_ts()
|
||||||
|
ttl = int(ttl)
|
||||||
|
expires = now + ttl
|
||||||
|
source_query = {'p_q': utils.scope_queue_name(source, project)}
|
||||||
|
target_source = self._queue_collection.find_one(source_query,
|
||||||
|
fields={'m': 1,
|
||||||
|
'_id': 0})
|
||||||
|
if target_source is None:
|
||||||
|
raise errors.QueueDoesNotExist(target_source, project)
|
||||||
|
|
||||||
|
try:
|
||||||
|
subscription_id = self._collection.insert({'s': source,
|
||||||
|
'u': subscriber,
|
||||||
|
't': ttl,
|
||||||
|
'e': expires,
|
||||||
|
'o': options,
|
||||||
|
'p': project})
|
||||||
|
return subscription_id
|
||||||
|
except pymongo.errors.DuplicateKeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@utils.raises_conn_error
|
||||||
|
def exists(self, queue, subscription_id, project=None):
|
||||||
|
return self._collection.find_one({'_id': utils.to_oid(subscription_id),
|
||||||
|
'p': project}) is not None
|
||||||
|
|
||||||
|
@utils.raises_conn_error
|
||||||
|
def update(self, queue, subscription_id, project=None, **kwargs):
|
||||||
|
names = ('subscriber', 'ttl', 'options')
|
||||||
|
key_transform = lambda x: 'u' if x == 'subscriber' else x[0]
|
||||||
|
fields = common_utils.fields(kwargs, names,
|
||||||
|
pred=lambda x: x is not None,
|
||||||
|
key_transform=key_transform)
|
||||||
|
assert fields, ('`subscriber`, `ttl`, '
|
||||||
|
'or `options` not found in kwargs')
|
||||||
|
|
||||||
|
res = self._collection.update({'_id': utils.to_oid(subscription_id),
|
||||||
|
'p': project},
|
||||||
|
{'$set': fields},
|
||||||
|
upsert=False)
|
||||||
|
|
||||||
|
if not res['updatedExisting']:
|
||||||
|
raise errors.SubscriptionDoesNotExist(subscription_id)
|
||||||
|
|
||||||
|
@utils.raises_conn_error
|
||||||
|
def delete(self, queue, subscription_id, project=None):
|
||||||
|
self._collection.remove({'_id': utils.to_oid(subscription_id),
|
||||||
|
'p': project}, w=0)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize(record):
|
||||||
|
ret = {
|
||||||
|
'id': str(record['_id']),
|
||||||
|
'source': record['s'],
|
||||||
|
'subscriber': record['u'],
|
||||||
|
'ttl': record['t'],
|
||||||
|
'options': record['o']
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
@ -23,7 +23,7 @@ from zaqar.storage import base
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
_PIPELINE_RESOURCES = ('queue', 'message', 'claim')
|
_PIPELINE_RESOURCES = ('queue', 'message', 'claim', 'subscription')
|
||||||
|
|
||||||
_PIPELINE_CONFIGS = tuple((
|
_PIPELINE_CONFIGS = tuple((
|
||||||
cfg.ListOpt(resource + '_pipeline', default=[],
|
cfg.ListOpt(resource + '_pipeline', default=[],
|
||||||
@ -122,3 +122,9 @@ class DataDriver(base.DataDriverBase):
|
|||||||
stages = _get_storage_pipeline('claim', self.conf)
|
stages = _get_storage_pipeline('claim', self.conf)
|
||||||
stages.append(self._storage.claim_controller)
|
stages.append(self._storage.claim_controller)
|
||||||
return stages
|
return stages
|
||||||
|
|
||||||
|
@decorators.lazy_property(write=False)
|
||||||
|
def subscription_controller(self):
|
||||||
|
stages = _get_storage_pipeline('subscription', self.conf)
|
||||||
|
stages.append(self._storage.subscription_controller)
|
||||||
|
return stages
|
||||||
|
@ -121,6 +121,10 @@ class DataDriver(storage.DataDriverBase):
|
|||||||
def claim_controller(self):
|
def claim_controller(self):
|
||||||
return ClaimController(self._pool_catalog)
|
return ClaimController(self._pool_catalog)
|
||||||
|
|
||||||
|
@decorators.lazy_property(write=False)
|
||||||
|
def subscription_controller(self):
|
||||||
|
return SubscriptionController(self._pool_catalog)
|
||||||
|
|
||||||
|
|
||||||
class QueueController(storage.Queue):
|
class QueueController(storage.Queue):
|
||||||
"""Routes operations to a queue controller in the appropriate pool.
|
"""Routes operations to a queue controller in the appropriate pool.
|
||||||
@ -345,6 +349,54 @@ class ClaimController(storage.Claim):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionController(storage.Subscription):
|
||||||
|
"""Controller to facilitate processing for subscription operations."""
|
||||||
|
|
||||||
|
_resource_name = 'subscription'
|
||||||
|
|
||||||
|
def __init__(self, pool_catalog):
|
||||||
|
super(SubscriptionController, self).__init__(pool_catalog)
|
||||||
|
self._pool_catalog = pool_catalog
|
||||||
|
self._get_controller = self._pool_catalog.get_subscription_controller
|
||||||
|
|
||||||
|
def list(self, queue, project=None, marker=None,
|
||||||
|
limit=storage.DEFAULT_SUBSCRIPTIONS_PER_PAGE):
|
||||||
|
control = self._get_controller(queue, project)
|
||||||
|
if control:
|
||||||
|
return control.list(queue, project=project,
|
||||||
|
marker=marker, limit=limit)
|
||||||
|
|
||||||
|
def get(self, queue, subscription_id, project=None):
|
||||||
|
control = self._get_controller(queue, project)
|
||||||
|
if control:
|
||||||
|
return control.get(queue, subscription_id, project=project)
|
||||||
|
|
||||||
|
def create(self, queue, subscriber, ttl, options, project=None):
|
||||||
|
control = self._get_controller(queue, project)
|
||||||
|
if control:
|
||||||
|
return control.post(queue, subscriber,
|
||||||
|
ttl, options,
|
||||||
|
project=project)
|
||||||
|
|
||||||
|
def update(self, queue, subscription_id, project=None, **kwargs):
|
||||||
|
control = self._get_controller(queue, project)
|
||||||
|
if control:
|
||||||
|
return control.update(queue, subscription_id,
|
||||||
|
project=project, **kwargs)
|
||||||
|
|
||||||
|
def delete(self, queue, subscription_id, project=None):
|
||||||
|
control = self._get_controller(queue, project)
|
||||||
|
if control:
|
||||||
|
return control.delete(queue, subscription_id,
|
||||||
|
project=project)
|
||||||
|
|
||||||
|
def exists(self, queue, subscription_id, project=None):
|
||||||
|
control = self._get_controller(queue, project)
|
||||||
|
if control:
|
||||||
|
return control.exists(queue, subscription_id,
|
||||||
|
project=project)
|
||||||
|
|
||||||
|
|
||||||
class Catalog(object):
|
class Catalog(object):
|
||||||
"""Represents the mapping between queues and pool drivers."""
|
"""Represents the mapping between queues and pool drivers."""
|
||||||
|
|
||||||
@ -491,6 +543,21 @@ class Catalog(object):
|
|||||||
target = self.lookup(queue, project)
|
target = self.lookup(queue, project)
|
||||||
return target and target.claim_controller
|
return target and target.claim_controller
|
||||||
|
|
||||||
|
def get_subscription_controller(self, queue, project=None):
|
||||||
|
"""Lookup the subscription controller for the given queue and project.
|
||||||
|
|
||||||
|
:param queue: Name of the queue for which to find a pool
|
||||||
|
:param project: Project to which the queue belongs, or
|
||||||
|
None to specify the "global" or "generic" project.
|
||||||
|
|
||||||
|
:returns: The subscription controller associated with the data driver
|
||||||
|
for the pool containing (queue, project) or None if this doesn't
|
||||||
|
exist.
|
||||||
|
:rtype: Maybe SubscriptionController
|
||||||
|
"""
|
||||||
|
target = self.lookup(queue, project)
|
||||||
|
return target and target.subscription_controller
|
||||||
|
|
||||||
def lookup(self, queue, project=None):
|
def lookup(self, queue, project=None):
|
||||||
"""Lookup a pool driver for the given queue and project.
|
"""Lookup a pool driver for the given queue and project.
|
||||||
|
|
||||||
|
@ -206,6 +206,10 @@ class DataDriver(storage.DataDriverBase):
|
|||||||
def claim_controller(self):
|
def claim_controller(self):
|
||||||
return controllers.ClaimController(self)
|
return controllers.ClaimController(self)
|
||||||
|
|
||||||
|
@decorators.lazy_property(write=False)
|
||||||
|
def subscription_controller(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class ControlDriver(storage.ControlDriverBase):
|
class ControlDriver(storage.ControlDriverBase):
|
||||||
|
|
||||||
@ -234,6 +238,10 @@ class ControlDriver(storage.ControlDriverBase):
|
|||||||
def flavors_controller(self):
|
def flavors_controller(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subscriptions_controller(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
def _get_redis_client(driver):
|
def _get_redis_client(driver):
|
||||||
conf = driver.redis_conf
|
conf = driver.redis_conf
|
||||||
|
@ -128,6 +128,10 @@ class DataDriver(storage.DataDriverBase):
|
|||||||
def claim_controller(self):
|
def claim_controller(self):
|
||||||
return controllers.ClaimController(self)
|
return controllers.ClaimController(self)
|
||||||
|
|
||||||
|
@decorators.lazy_property(write=False)
|
||||||
|
def subscription_controller(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def is_alive(self):
|
def is_alive(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -190,3 +194,7 @@ class ControlDriver(storage.ControlDriverBase):
|
|||||||
def flavors_controller(self):
|
def flavors_controller(self):
|
||||||
# NOTE(flaper87): Needed to avoid `abc` errors.
|
# NOTE(flaper87): Needed to avoid `abc` errors.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subscriptions_controller(self):
|
||||||
|
pass
|
||||||
|
@ -46,6 +46,10 @@ class DataDriver(storage.DataDriverBase):
|
|||||||
def claim_controller(self):
|
def claim_controller(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subscription_controller(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ControlDriver(storage.ControlDriverBase):
|
class ControlDriver(storage.ControlDriverBase):
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Copyright (c) 2013 Red Hat, Inc.
|
# Copyright (c) 2013 Red Hat, Inc.
|
||||||
|
# Copyright (c) 2014 Catalyst IT Ltd.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -929,6 +930,127 @@ class ClaimControllerTest(ControllerBaseTest):
|
|||||||
project=self.project)
|
project=self.project)
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionControllerTest(ControllerBaseTest):
|
||||||
|
"""Subscriptions Controller base tests.
|
||||||
|
|
||||||
|
"""
|
||||||
|
queue_name = 'test_queue'
|
||||||
|
controller_base_class = storage.Subscription
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(SubscriptionControllerTest, self).setUp()
|
||||||
|
self.subscription_controller = self.driver.subscription_controller
|
||||||
|
|
||||||
|
# Lets create a queue as the source of subscription
|
||||||
|
self.queue_controller = self.driver.queue_controller
|
||||||
|
self.queue_controller.create(self.queue_name, project=self.project)
|
||||||
|
|
||||||
|
self.source = self.queue_name
|
||||||
|
self.subscriber = 'http://trigger.me'
|
||||||
|
self.ttl = 600
|
||||||
|
self.options = {'uri': 'http://fake.com'}
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.queue_controller.delete(self.queue_name, project=self.project)
|
||||||
|
super(SubscriptionControllerTest, self).tearDown()
|
||||||
|
|
||||||
|
def test_list(self):
|
||||||
|
for s in six.moves.xrange(15):
|
||||||
|
subscriber = 'http://fake_{0}'.format(s)
|
||||||
|
self.subscription_controller.create(self.source,
|
||||||
|
subscriber,
|
||||||
|
self.ttl,
|
||||||
|
self.options,
|
||||||
|
project=self.project)
|
||||||
|
|
||||||
|
interaction = self.subscription_controller.list(self.source,
|
||||||
|
project=self.project)
|
||||||
|
subscriptions = list(next(interaction))
|
||||||
|
|
||||||
|
self.assertEqual(all(map(lambda s: 'source' in s and 'subscriber' in s,
|
||||||
|
subscriptions)), True)
|
||||||
|
self.assertEqual(len(subscriptions), 10)
|
||||||
|
|
||||||
|
interaction = (self.subscription_controller.list(self.source,
|
||||||
|
project=self.project,
|
||||||
|
marker=next(interaction)))
|
||||||
|
subscriptions = list(next(interaction))
|
||||||
|
|
||||||
|
self.assertEqual(all(map(lambda s: 'source' in s and 'subscriber' in s,
|
||||||
|
subscriptions)), True)
|
||||||
|
self.assertEqual(len(subscriptions), 5)
|
||||||
|
|
||||||
|
def test_get_raises_if_subscription_does_not_exist(self):
|
||||||
|
self.assertRaises(errors.SubscriptionDoesNotExist,
|
||||||
|
self.subscription_controller.get,
|
||||||
|
self.queue_name,
|
||||||
|
'notexists',
|
||||||
|
project=self.project)
|
||||||
|
|
||||||
|
def test_lifecycle(self):
|
||||||
|
s_id = self.subscription_controller.create(self.source,
|
||||||
|
self.subscriber,
|
||||||
|
self.ttl,
|
||||||
|
self.options,
|
||||||
|
project=self.project)
|
||||||
|
|
||||||
|
subscription = self.subscription_controller.get(self.queue_name,
|
||||||
|
s_id,
|
||||||
|
self.project)
|
||||||
|
|
||||||
|
self.assertEqual(self.source,
|
||||||
|
subscription['source'])
|
||||||
|
self.assertEqual(self.subscriber,
|
||||||
|
subscription['subscriber'])
|
||||||
|
|
||||||
|
exist = self.subscription_controller.exists(self.queue_name,
|
||||||
|
s_id,
|
||||||
|
self.project)
|
||||||
|
|
||||||
|
self.assertTrue(exist)
|
||||||
|
|
||||||
|
self.subscription_controller.update(self.queue_name,
|
||||||
|
s_id,
|
||||||
|
project=self.project,
|
||||||
|
subscriber='http://a.com'
|
||||||
|
)
|
||||||
|
|
||||||
|
updated = self.subscription_controller.get(self.queue_name,
|
||||||
|
s_id,
|
||||||
|
self.project)
|
||||||
|
|
||||||
|
self.assertEqual('http://a.com', updated['subscriber'])
|
||||||
|
|
||||||
|
self.subscription_controller.delete(self.queue_name,
|
||||||
|
s_id, project=self.project)
|
||||||
|
self.assertRaises(errors.SubscriptionDoesNotExist,
|
||||||
|
self.subscription_controller.get,
|
||||||
|
self.queue_name, s_id)
|
||||||
|
|
||||||
|
def test_create_existed(self):
|
||||||
|
self.subscription_controller.create(self.source,
|
||||||
|
self.subscriber,
|
||||||
|
self.ttl,
|
||||||
|
self.options,
|
||||||
|
project=self.project)
|
||||||
|
|
||||||
|
s_id = self.subscription_controller.create(self.source,
|
||||||
|
self.subscriber,
|
||||||
|
self.ttl,
|
||||||
|
self.options,
|
||||||
|
project=self.project)
|
||||||
|
self.assertIsNone(s_id)
|
||||||
|
|
||||||
|
def test_nonexist_source(self):
|
||||||
|
self.assertRaises(errors.QueueDoesNotExist,
|
||||||
|
self.subscription_controller.create,
|
||||||
|
'fake_queue_name',
|
||||||
|
self.subscriber,
|
||||||
|
self.ttl,
|
||||||
|
self.options,
|
||||||
|
self.project)
|
||||||
|
|
||||||
|
|
||||||
class PoolsControllerTest(ControllerBaseTest):
|
class PoolsControllerTest(ControllerBaseTest):
|
||||||
"""Pools Controller base tests.
|
"""Pools Controller base tests.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user