Merge "Remove api vi support 2"
This commit is contained in:
commit
e83ca3941a
@ -1,388 +0,0 @@
|
||||
# Copyright (c) 2013 Red Hat, 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.
|
||||
|
||||
from zaqar.common.api import api
|
||||
from zaqar.common import consts
|
||||
|
||||
|
||||
class RequestSchema(api.Api):
|
||||
|
||||
headers = {
|
||||
'User-Agent': {'type': 'string'},
|
||||
'Date': {'type': 'string'},
|
||||
'Accept': {'type': 'string'},
|
||||
'Client-ID': {'type': 'string'},
|
||||
'X-Project-ID': {'type': 'string'},
|
||||
'X-Auth-Token': {'type': 'string'}
|
||||
}
|
||||
|
||||
schema = {
|
||||
|
||||
# Base
|
||||
'get_home_doc': {
|
||||
'properties': {
|
||||
'action': {'enum': ['get_home_doc']},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers'],
|
||||
'admin': True,
|
||||
},
|
||||
|
||||
'check_node_health': {
|
||||
'properties': {
|
||||
'action': {'enum': ['check_node_health']},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers'],
|
||||
'admin': True,
|
||||
},
|
||||
|
||||
'ping_node': {
|
||||
'properties': {
|
||||
'action': {'enum': ['ping_node']},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers'],
|
||||
'admin': True,
|
||||
},
|
||||
'authenticate': {
|
||||
'properties': {
|
||||
'action': {'enum': ['authenticate']},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['X-Project-ID', 'X-Auth-Token']
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers'],
|
||||
},
|
||||
|
||||
# Queues
|
||||
consts.QUEUE_LIST: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.QUEUE_LIST]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'marker': {'type': 'string'},
|
||||
'limit': {'type': 'integer'},
|
||||
'detailed': {'type': 'boolean'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers']
|
||||
},
|
||||
|
||||
consts.QUEUE_CREATE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.QUEUE_CREATE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.QUEUE_DELETE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.QUEUE_DELETE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
},
|
||||
'required': ['queue_name']
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.QUEUE_GET: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.QUEUE_GET]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.QUEUE_GET_STATS: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.QUEUE_GET_STATS]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body'],
|
||||
'admin': True
|
||||
},
|
||||
|
||||
# Messages
|
||||
consts.MESSAGE_LIST: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_LIST]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'marker': {'type': 'string'},
|
||||
'limit': {'type': 'integer'},
|
||||
'echo': {'type': 'boolean'},
|
||||
'include_claimed': {'type': 'boolean'},
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.MESSAGE_GET: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_GET]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'message_id': {'type': 'string'},
|
||||
},
|
||||
'required': ['queue_name', 'message_id'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.MESSAGE_GET_MANY: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_GET_MANY]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'message_ids': {'type': 'array'},
|
||||
},
|
||||
'required': ['queue_name', 'message_ids'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.MESSAGE_POST: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_POST]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'messages': {'type': 'array'},
|
||||
},
|
||||
'required': ['queue_name', 'messages'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.MESSAGE_DELETE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_DELETE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'message_id': {'type': 'string'},
|
||||
'claim_id': {'type': 'string'}
|
||||
},
|
||||
'required': ['queue_name', 'message_id'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.MESSAGE_DELETE_MANY: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_DELETE_MANY]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'message_ids': {'type': 'array'},
|
||||
'claim_ids': {'type': 'array'},
|
||||
'pop': {'type': 'integer'}
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
# Claims
|
||||
consts.CLAIM_CREATE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.CLAIM_CREATE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'limit': {'type': 'integer'},
|
||||
'ttl': {'type': 'integer'},
|
||||
'grace': {'type': 'integer'}
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.CLAIM_GET: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.CLAIM_GET]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'claim_id': {'type': 'string'}
|
||||
},
|
||||
'required': ['queue_name', 'claim_id'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.CLAIM_UPDATE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.CLAIM_UPDATE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'claim_id': {'type': 'string'},
|
||||
'ttl': {'type': 'integer'}
|
||||
},
|
||||
'required': ['queue_name', 'claim_id'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.CLAIM_DELETE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.CLAIM_DELETE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'claim_id': {'type': 'string'}
|
||||
},
|
||||
'required': ['queue_name', 'claim_id'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
}
|
@ -1,301 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from zaqar.common.api import api
|
||||
from zaqar.common import consts
|
||||
|
||||
|
||||
class ResponseSchema(api.Api):
|
||||
|
||||
"""Define validation schema for json response."""
|
||||
|
||||
def __init__(self, limits):
|
||||
self.limits = limits
|
||||
|
||||
age = {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
}
|
||||
|
||||
message = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"href": {
|
||||
"type": "string",
|
||||
"pattern": r"^(/v1/queues/[a-zA-Z0-9_-]"
|
||||
r"{1,64}/messages/[a-zA-Z0-9_-]+)$"
|
||||
},
|
||||
"age": age,
|
||||
"ttl": {
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": self.limits.max_message_ttl
|
||||
},
|
||||
|
||||
"body": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": ["href", "ttl", "age", "body"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
claim_href = {
|
||||
"type": "string",
|
||||
"pattern": r"^(/v1/queues/[a-zA-Z0-9_-]{1,64}"
|
||||
r"/messages/[a-zA-Z0-9_-]+)"
|
||||
r"\?claim_id=[a-zA-Z0-9_-]+$"
|
||||
}
|
||||
|
||||
self.schema = {
|
||||
consts.QUEUE_LIST: {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'links': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'rel': {
|
||||
'type': 'string',
|
||||
'enum': ['next'],
|
||||
},
|
||||
'href': {
|
||||
'type': 'string',
|
||||
"pattern": r"^/v1/queues\?",
|
||||
}
|
||||
},
|
||||
'required': ['rel', 'href'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
'minItems': 1,
|
||||
'maxItems': 1,
|
||||
},
|
||||
'queues': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'pattern': r'^[a-zA-Z0-9_-]{1,64}$'
|
||||
},
|
||||
'href': {
|
||||
'type': 'string',
|
||||
'pattern': r'^/v1/queues/'
|
||||
r'[a-zA-Z0-9_-]{1,64}$',
|
||||
},
|
||||
'metadata': {
|
||||
'type': 'object',
|
||||
}
|
||||
},
|
||||
'required': ['name', 'href'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
'minItems': 1,
|
||||
'maxItems': self.limits.max_queues_per_page,
|
||||
}
|
||||
},
|
||||
'required': ['links', 'queues'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
consts.QUEUE_GET_STATS: {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'messages': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'free': {
|
||||
'type': 'number',
|
||||
'minimum': 0
|
||||
},
|
||||
'claimed': {
|
||||
'type': 'number',
|
||||
'minimum': 0
|
||||
},
|
||||
'total': {
|
||||
'type': 'number',
|
||||
'minimum': 0
|
||||
},
|
||||
'oldest': {
|
||||
'type': 'object'
|
||||
},
|
||||
'newest': {
|
||||
'type': 'object'
|
||||
}
|
||||
|
||||
},
|
||||
'required': ['free', 'claimed', 'total'],
|
||||
'additionalProperties': False
|
||||
}
|
||||
},
|
||||
'required': ['messages'],
|
||||
'additionalProperties': False
|
||||
},
|
||||
|
||||
consts.POOL_LIST: {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'links': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'rel': {
|
||||
'type': 'string'
|
||||
},
|
||||
'href': {
|
||||
'type': 'string',
|
||||
'pattern': r'^/v1/pools\?'
|
||||
}
|
||||
},
|
||||
'required': ['rel', 'href'],
|
||||
'additionalProperties': False
|
||||
}
|
||||
},
|
||||
'pools': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'href': {
|
||||
'type': 'string',
|
||||
'pattern': r'^/v1/'
|
||||
r'pools/[a-zA-Z0-9_-]{1,64}$'
|
||||
},
|
||||
'weight': {
|
||||
'type': 'number',
|
||||
'minimum': -1
|
||||
},
|
||||
'name': {
|
||||
'type': 'string'
|
||||
},
|
||||
'uri': {
|
||||
'type': 'string'
|
||||
},
|
||||
'options': {
|
||||
'type': 'object',
|
||||
'additionalProperties': True
|
||||
}
|
||||
},
|
||||
'required': ['href', 'weight', 'uri'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
}
|
||||
},
|
||||
'required': ['links', 'pools'],
|
||||
'additionalProperties': False
|
||||
},
|
||||
|
||||
consts.MESSAGE_LIST: {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'links': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'rel': {
|
||||
'type': 'string'
|
||||
},
|
||||
'href': {
|
||||
'type': 'string',
|
||||
'pattern': r'^/v1/queues/[a-zA-Z0-9_-]+'
|
||||
r'/messages\?(.)*$'
|
||||
}
|
||||
},
|
||||
'required': ['rel', 'href'],
|
||||
'additionalProperties': False
|
||||
}
|
||||
},
|
||||
'messages': {
|
||||
"type": "array",
|
||||
"items": message,
|
||||
"minItems": 1,
|
||||
"maxItems": self.limits.max_messages_per_claim_or_pop
|
||||
}
|
||||
}
|
||||
},
|
||||
consts.MESSAGE_GET_MANY: {
|
||||
"type": "array",
|
||||
"items": message,
|
||||
"minItems": 1,
|
||||
"maxItems": self.limits.max_messages_per_page
|
||||
},
|
||||
|
||||
consts.CLAIM_CREATE: {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"href": claim_href,
|
||||
"ttl": {
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": self.limits.max_message_ttl
|
||||
},
|
||||
"age": age,
|
||||
"body": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": ["href", "ttl", "age", "body"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": self.limits.max_messages_per_page
|
||||
},
|
||||
|
||||
consts.CLAIM_GET: {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'age': age,
|
||||
'ttl': {
|
||||
'type': 'number',
|
||||
'minimum': 0,
|
||||
'maximum': self.limits.max_claim_ttl
|
||||
},
|
||||
'href': {
|
||||
'type': 'string',
|
||||
'pattern': r'^/v1/queues/[a-zA-Z0-9_-]+'
|
||||
r'/claims/[a-zA-Z0-9_-]+$'
|
||||
},
|
||||
'messages': {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"href": claim_href,
|
||||
"ttl": {
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": self.limits.max_message_ttl
|
||||
},
|
||||
"age": age,
|
||||
"body": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": ["href", "ttl", "age", "body"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": self.limits.max_messages_per_page
|
||||
}
|
||||
},
|
||||
'required': ['age', 'ttl', 'messages', 'href'],
|
||||
'additionalProperties': False
|
||||
}
|
||||
}
|
@ -13,16 +13,378 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from zaqar.api.v1 import request as v1
|
||||
from zaqar.common.api import api
|
||||
from zaqar.common import consts
|
||||
|
||||
|
||||
class RequestSchema(v1.RequestSchema):
|
||||
class RequestSchema(api.Api):
|
||||
|
||||
headers = v1.RequestSchema.headers
|
||||
schema = v1.RequestSchema.schema
|
||||
headers = {
|
||||
'User-Agent': {'type': 'string'},
|
||||
'Date': {'type': 'string'},
|
||||
'Accept': {'type': 'string'},
|
||||
'Client-ID': {'type': 'string'},
|
||||
'X-Project-ID': {'type': 'string'},
|
||||
'X-Auth-Token': {'type': 'string'}
|
||||
}
|
||||
|
||||
schema.update({
|
||||
schema = {
|
||||
|
||||
# Base
|
||||
'get_home_doc': {
|
||||
'properties': {
|
||||
'action': {'enum': ['get_home_doc']},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers'],
|
||||
'admin': True,
|
||||
},
|
||||
|
||||
'check_node_health': {
|
||||
'properties': {
|
||||
'action': {'enum': ['check_node_health']},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers'],
|
||||
'admin': True,
|
||||
},
|
||||
|
||||
'ping_node': {
|
||||
'properties': {
|
||||
'action': {'enum': ['ping_node']},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers'],
|
||||
'admin': True,
|
||||
},
|
||||
'authenticate': {
|
||||
'properties': {
|
||||
'action': {'enum': ['authenticate']},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['X-Project-ID', 'X-Auth-Token']
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers'],
|
||||
},
|
||||
|
||||
# Queues
|
||||
consts.QUEUE_LIST: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.QUEUE_LIST]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'marker': {'type': 'string'},
|
||||
'limit': {'type': 'integer'},
|
||||
'detailed': {'type': 'boolean'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers']
|
||||
},
|
||||
|
||||
consts.QUEUE_CREATE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.QUEUE_CREATE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.QUEUE_DELETE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.QUEUE_DELETE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
},
|
||||
'required': ['queue_name']
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.QUEUE_GET: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.QUEUE_GET]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.QUEUE_GET_STATS: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.QUEUE_GET_STATS]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body'],
|
||||
'admin': True
|
||||
},
|
||||
|
||||
# Messages
|
||||
consts.MESSAGE_LIST: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_LIST]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'marker': {'type': 'string'},
|
||||
'limit': {'type': 'integer'},
|
||||
'echo': {'type': 'boolean'},
|
||||
'include_claimed': {'type': 'boolean'},
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.MESSAGE_GET: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_GET]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'message_id': {'type': 'string'},
|
||||
},
|
||||
'required': ['queue_name', 'message_id'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.MESSAGE_GET_MANY: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_GET_MANY]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'message_ids': {'type': 'array'},
|
||||
},
|
||||
'required': ['queue_name', 'message_ids'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.MESSAGE_POST: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_POST]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'messages': {'type': 'array'},
|
||||
},
|
||||
'required': ['queue_name', 'messages'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.MESSAGE_DELETE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_DELETE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'message_id': {'type': 'string'},
|
||||
'claim_id': {'type': 'string'}
|
||||
},
|
||||
'required': ['queue_name', 'message_id'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.MESSAGE_DELETE_MANY: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.MESSAGE_DELETE_MANY]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'message_ids': {'type': 'array'},
|
||||
'claim_ids': {'type': 'array'},
|
||||
'pop': {'type': 'integer'}
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
# Claims
|
||||
consts.CLAIM_CREATE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.CLAIM_CREATE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'limit': {'type': 'integer'},
|
||||
'ttl': {'type': 'integer'},
|
||||
'grace': {'type': 'integer'}
|
||||
},
|
||||
'required': ['queue_name'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.CLAIM_GET: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.CLAIM_GET]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'claim_id': {'type': 'string'}
|
||||
},
|
||||
'required': ['queue_name', 'claim_id'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.CLAIM_UPDATE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.CLAIM_UPDATE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'claim_id': {'type': 'string'},
|
||||
'ttl': {'type': 'integer'}
|
||||
},
|
||||
'required': ['queue_name', 'claim_id'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
consts.CLAIM_DELETE: {
|
||||
'properties': {
|
||||
'action': {'enum': [consts.CLAIM_DELETE]},
|
||||
'headers': {
|
||||
'type': 'object',
|
||||
'properties': headers,
|
||||
'required': ['Client-ID', 'X-Project-ID']
|
||||
},
|
||||
'body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'queue_name': {'type': 'string'},
|
||||
'claim_id': {'type': 'string'}
|
||||
},
|
||||
'required': ['queue_name', 'claim_id'],
|
||||
}
|
||||
},
|
||||
'required': ['action', 'headers', 'body']
|
||||
},
|
||||
|
||||
# Pools
|
||||
consts.POOL_LIST: {
|
||||
@ -241,4 +603,4 @@ class RequestSchema(v1.RequestSchema):
|
||||
'required': ['action', 'headers', 'body'],
|
||||
'admin': True,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -19,23 +19,6 @@ from oslo_serialization import jsonutils
|
||||
from zaqar.tests.unit.transport.wsgi import base
|
||||
|
||||
EXPECTED_VERSIONS = [
|
||||
{
|
||||
'id': '1',
|
||||
'status': 'DEPRECATED',
|
||||
'updated': '2014-9-11T17:47:05Z',
|
||||
'media-types': [
|
||||
{
|
||||
'base': 'application/json',
|
||||
'type': 'application/vnd.openstack.messaging-v1+json'
|
||||
}
|
||||
],
|
||||
'links': [
|
||||
{
|
||||
'href': '/v1/',
|
||||
'rel': 'self'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': '1.1',
|
||||
'status': 'DEPRECATED',
|
||||
@ -82,5 +65,5 @@ class TestVersion(base.TestBase):
|
||||
versions = jsonutils.loads(response[0])['versions']
|
||||
|
||||
self.assertEqual(falcon.HTTP_300, self.srmock.status)
|
||||
self.assertEqual(3, len(versions))
|
||||
self.assertEqual(2, len(versions))
|
||||
self.assertEqual(EXPECTED_VERSIONS, versions)
|
||||
|
@ -31,7 +31,6 @@ from zaqar.transport.middleware import auth
|
||||
from zaqar.transport.middleware import cors
|
||||
from zaqar.transport.middleware import profile
|
||||
from zaqar.transport import validation
|
||||
from zaqar.transport.wsgi import v1_0
|
||||
from zaqar.transport.wsgi import v1_1
|
||||
from zaqar.transport.wsgi import v2_0
|
||||
from zaqar.transport.wsgi import version
|
||||
@ -108,7 +107,6 @@ class Driver(transport.DriverBase):
|
||||
"""Initialize hooks and URI routes to resources."""
|
||||
|
||||
catalog = [
|
||||
('/v1', v1_0.public_endpoints(self, self._conf)),
|
||||
('/v1.1', v1_1.public_endpoints(self, self._conf)),
|
||||
('/v2', v2_0.public_endpoints(self, self._conf)),
|
||||
('/', [('', version.Resource())])
|
||||
@ -116,7 +114,6 @@ class Driver(transport.DriverBase):
|
||||
|
||||
if self._conf.admin_mode:
|
||||
catalog.extend([
|
||||
('/v1', v1_0.private_endpoints(self, self._conf)),
|
||||
('/v1.1', v1_1.private_endpoints(self, self._conf)),
|
||||
('/v2', v2_0.private_endpoints(self, self._conf)),
|
||||
])
|
||||
|
@ -1,110 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from zaqar.common import decorators
|
||||
from zaqar.transport.wsgi.v1_0 import claims
|
||||
from zaqar.transport.wsgi.v1_0 import health
|
||||
from zaqar.transport.wsgi.v1_0 import homedoc
|
||||
from zaqar.transport.wsgi.v1_0 import messages
|
||||
from zaqar.transport.wsgi.v1_0 import metadata
|
||||
from zaqar.transport.wsgi.v1_0 import pools
|
||||
from zaqar.transport.wsgi.v1_0 import queues
|
||||
from zaqar.transport.wsgi.v1_0 import stats
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
VERSION = {
|
||||
'id': '1',
|
||||
'status': 'DEPRECATED',
|
||||
'updated': '2014-9-11T17:47:05Z',
|
||||
'media-types': [
|
||||
{
|
||||
'base': 'application/json',
|
||||
'type': 'application/vnd.openstack.messaging-v1+json'
|
||||
}
|
||||
],
|
||||
'links': [
|
||||
{
|
||||
'href': '/v1/',
|
||||
'rel': 'self'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@decorators.api_version_manager(VERSION)
|
||||
def public_endpoints(driver, conf):
|
||||
queue_controller = driver._storage.queue_controller
|
||||
message_controller = driver._storage.message_controller
|
||||
claim_controller = driver._storage.claim_controller
|
||||
|
||||
return [
|
||||
# Home
|
||||
('/',
|
||||
homedoc.Resource()),
|
||||
|
||||
# Queues Endpoints
|
||||
('/queues',
|
||||
queues.CollectionResource(driver._validate,
|
||||
queue_controller)),
|
||||
('/queues/{queue_name}',
|
||||
queues.ItemResource(queue_controller,
|
||||
message_controller)),
|
||||
('/queues/{queue_name}/stats',
|
||||
stats.Resource(queue_controller)),
|
||||
('/queues/{queue_name}/metadata',
|
||||
metadata.Resource(driver._wsgi_conf, driver._validate,
|
||||
queue_controller)),
|
||||
|
||||
# Messages Endpoints
|
||||
('/queues/{queue_name}/messages',
|
||||
messages.CollectionResource(driver._wsgi_conf,
|
||||
driver._validate,
|
||||
message_controller)),
|
||||
('/queues/{queue_name}/messages/{message_id}',
|
||||
messages.ItemResource(message_controller)),
|
||||
|
||||
# Claims Endpoints
|
||||
('/queues/{queue_name}/claims',
|
||||
claims.CollectionResource(driver._wsgi_conf,
|
||||
driver._validate,
|
||||
claim_controller)),
|
||||
('/queues/{queue_name}/claims/{claim_id}',
|
||||
claims.ItemResource(driver._wsgi_conf,
|
||||
driver._validate,
|
||||
claim_controller)),
|
||||
|
||||
# Health
|
||||
('/health',
|
||||
health.Resource(driver._storage))
|
||||
]
|
||||
|
||||
|
||||
@decorators.api_version_manager(VERSION)
|
||||
def private_endpoints(driver, conf):
|
||||
if not conf.pooling:
|
||||
return []
|
||||
|
||||
pools_controller = driver._control.pools_controller
|
||||
|
||||
return [
|
||||
('/pools',
|
||||
pools.Listing(pools_controller)),
|
||||
('/pools/{pool}',
|
||||
pools.Resource(pools_controller)),
|
||||
]
|
@ -1,172 +0,0 @@
|
||||
# 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 falcon
|
||||
from oslo_log import log as logging
|
||||
|
||||
from zaqar.common import decorators
|
||||
from zaqar.i18n import _
|
||||
from zaqar.storage import errors as storage_errors
|
||||
from zaqar.transport import utils
|
||||
from zaqar.transport import validation
|
||||
from zaqar.transport.wsgi import errors as wsgi_errors
|
||||
from zaqar.transport.wsgi import utils as wsgi_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CLAIM_POST_SPEC = (('ttl', int, None), ('grace', int, None))
|
||||
CLAIM_PATCH_SPEC = (('ttl', int, None), ('grace', int, 0))
|
||||
|
||||
|
||||
class Resource(object):
|
||||
|
||||
__slots__ = ('_claim_controller', '_validate')
|
||||
|
||||
def __init__(self, wsgi_conf, validate, claim_controller):
|
||||
self._claim_controller = claim_controller
|
||||
self._validate = validate
|
||||
|
||||
|
||||
class CollectionResource(Resource):
|
||||
|
||||
@decorators.TransportLog("Claims collection")
|
||||
def on_post(self, req, resp, project_id, queue_name):
|
||||
# Check for an explicit limit on the # of messages to claim
|
||||
limit = req.get_param_as_int('limit')
|
||||
claim_options = {} if limit is None else {'limit': limit}
|
||||
|
||||
# Read claim metadata (e.g., TTL) and raise appropriate
|
||||
# HTTP errors as needed.
|
||||
document = wsgi_utils.deserialize(req.stream, req.content_length)
|
||||
metadata = wsgi_utils.sanitize(document, CLAIM_POST_SPEC)
|
||||
|
||||
# Claim some messages
|
||||
try:
|
||||
self._validate.claim_creation(metadata, limit=limit)
|
||||
cid, msgs = self._claim_controller.create(
|
||||
queue_name,
|
||||
metadata=metadata,
|
||||
project=project_id,
|
||||
**claim_options)
|
||||
|
||||
# Buffer claimed messages
|
||||
# TODO(kgriffs): optimize, along with serialization (below)
|
||||
resp_msgs = list(msgs)
|
||||
|
||||
except validation.ValidationFailed as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPBadRequestAPI(str(ex))
|
||||
|
||||
except Exception:
|
||||
description = _('Claim could not be created.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
# Serialize claimed messages, if any. This logic assumes
|
||||
# the storage driver returned well-formed messages.
|
||||
if len(resp_msgs) != 0:
|
||||
resp_msgs = [wsgi_utils.format_message_v1(
|
||||
msg, req.path.rpartition('/')[0], cid) for msg in resp_msgs]
|
||||
|
||||
resp.location = req.path + '/' + cid
|
||||
resp.text = utils.to_json(resp_msgs)
|
||||
resp.status = falcon.HTTP_201
|
||||
else:
|
||||
resp.status = falcon.HTTP_204
|
||||
|
||||
|
||||
class ItemResource(Resource):
|
||||
|
||||
__slots__ = ('_claim_controller', '_validate')
|
||||
|
||||
def __init__(self, wsgi_conf, validate, claim_controller):
|
||||
self._claim_controller = claim_controller
|
||||
self._validate = validate
|
||||
|
||||
@decorators.TransportLog("Claim item")
|
||||
def on_get(self, req, resp, project_id, queue_name, claim_id):
|
||||
try:
|
||||
meta, msgs = self._claim_controller.get(
|
||||
queue_name,
|
||||
claim_id=claim_id,
|
||||
project=project_id)
|
||||
|
||||
# Buffer claimed messages
|
||||
# TODO(kgriffs): Optimize along with serialization (see below)
|
||||
meta['messages'] = list(msgs)
|
||||
|
||||
except storage_errors.DoesNotExist as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPNotFound(str(ex))
|
||||
except Exception:
|
||||
description = _('Claim could not be queried.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
# Serialize claimed messages
|
||||
# TODO(kgriffs): Optimize
|
||||
meta['messages'] = [wsgi_utils.format_message_v1(
|
||||
msg, req.path.rsplit('/', 2)[0], meta['id'])
|
||||
for msg in meta['messages']]
|
||||
|
||||
meta['href'] = req.path
|
||||
del meta['id']
|
||||
|
||||
resp.content_location = req.relative_uri
|
||||
resp.text = utils.to_json(meta)
|
||||
# status defaults to 200
|
||||
|
||||
@decorators.TransportLog("Claim item")
|
||||
def on_patch(self, req, resp, project_id, queue_name, claim_id):
|
||||
# Read claim metadata (e.g., TTL) and raise appropriate
|
||||
# HTTP errors as needed.
|
||||
document = wsgi_utils.deserialize(req.stream, req.content_length)
|
||||
metadata = wsgi_utils.sanitize(document, CLAIM_PATCH_SPEC)
|
||||
|
||||
try:
|
||||
self._validate.claim_updating(metadata)
|
||||
self._claim_controller.update(queue_name,
|
||||
claim_id=claim_id,
|
||||
metadata=metadata,
|
||||
project=project_id)
|
||||
|
||||
resp.status = falcon.HTTP_204
|
||||
|
||||
except validation.ValidationFailed as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPBadRequestAPI(str(ex))
|
||||
|
||||
except storage_errors.DoesNotExist as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPNotFound(str(ex))
|
||||
|
||||
except Exception:
|
||||
description = _('Claim could not be updated.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
@decorators.TransportLog("Claim item")
|
||||
def on_delete(self, req, resp, project_id, queue_name, claim_id):
|
||||
try:
|
||||
self._claim_controller.delete(queue_name,
|
||||
claim_id=claim_id,
|
||||
project=project_id)
|
||||
|
||||
resp.status = falcon.HTTP_204
|
||||
|
||||
except Exception:
|
||||
description = _('Claim could not be deleted.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
@ -1,30 +0,0 @@
|
||||
# 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 falcon
|
||||
|
||||
|
||||
class Resource(object):
|
||||
|
||||
__slots__ = ('_driver',)
|
||||
|
||||
def __init__(self, driver):
|
||||
self._driver = driver
|
||||
|
||||
def on_get(self, req, resp, **kwargs):
|
||||
resp.status = (falcon.HTTP_204 if self._driver.is_alive()
|
||||
else falcon.HTTP_503)
|
||||
|
||||
def on_head(self, req, resp, **kwargs):
|
||||
resp.status = falcon.HTTP_204
|
@ -1,142 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
|
||||
# NOTE(kgriffs): http://tools.ietf.org/html/draft-nottingham-json-home-03
|
||||
JSON_HOME = {
|
||||
'resources': {
|
||||
# -----------------------------------------------------------------
|
||||
# Queues
|
||||
# -----------------------------------------------------------------
|
||||
'rel/queues': {
|
||||
'href-template': '/v1/queues{?marker,limit,detailed}',
|
||||
'href-vars': {
|
||||
'marker': 'param/marker',
|
||||
'limit': 'param/queue_limit',
|
||||
'detailed': 'param/detailed',
|
||||
},
|
||||
'hints': {
|
||||
'allow': ['GET'],
|
||||
'formats': {
|
||||
'application/json': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
'rel/queue': {
|
||||
'href-template': '/v1/queues/{queue_name}',
|
||||
'href-vars': {
|
||||
'queue_name': 'param/queue_name',
|
||||
},
|
||||
'hints': {
|
||||
'allow': ['GET', 'HEAD', 'PUT', 'DELETE'],
|
||||
'formats': {
|
||||
'application/json': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
'rel/queue-metadata': {
|
||||
'href-template': '/v1/queues/{queue_name}/metadata',
|
||||
'href-vars': {
|
||||
'queue_name': 'param/queue_name',
|
||||
},
|
||||
'hints': {
|
||||
'allow': ['GET', 'PUT'],
|
||||
'formats': {
|
||||
'application/json': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
'rel/queue-stats': {
|
||||
'href-template': '/v1/queues/{queue_name}/stats',
|
||||
'href-vars': {
|
||||
'queue_name': 'param/queue_name',
|
||||
},
|
||||
'hints': {
|
||||
'allow': ['GET'],
|
||||
'formats': {
|
||||
'application/json': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Messages
|
||||
# -----------------------------------------------------------------
|
||||
'rel/messages': {
|
||||
'href-template': ('/v1/queues/{queue_name}/messages'
|
||||
'{?marker,limit,echo,include_claimed}'),
|
||||
'href-vars': {
|
||||
'queue_name': 'param/queue_name',
|
||||
'marker': 'param/marker',
|
||||
'limit': 'param/messages_limit',
|
||||
'echo': 'param/echo',
|
||||
'include_claimed': 'param/include_claimed',
|
||||
},
|
||||
'hints': {
|
||||
'allow': ['GET'],
|
||||
'formats': {
|
||||
'application/json': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
'rel/post-messages': {
|
||||
'href-template': '/v1/queues/{queue_name}/messages',
|
||||
'href-vars': {
|
||||
'queue_name': 'param/queue_name',
|
||||
},
|
||||
'hints': {
|
||||
'allow': ['POST'],
|
||||
'formats': {
|
||||
'application/json': {},
|
||||
},
|
||||
'accept-post': ['application/json'],
|
||||
},
|
||||
},
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Claims
|
||||
# -----------------------------------------------------------------
|
||||
'rel/claim': {
|
||||
'href-template': '/v1/queues/{queue_name}/claims{?limit}',
|
||||
'href-vars': {
|
||||
'queue_name': 'param/queue_name',
|
||||
'limit': 'param/claim_limit',
|
||||
},
|
||||
'hints': {
|
||||
'allow': ['POST'],
|
||||
'formats': {
|
||||
'application/json': {},
|
||||
},
|
||||
'accept-post': ['application/json']
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Resource(object):
|
||||
|
||||
def __init__(self):
|
||||
document = jsonutils.dumps(JSON_HOME, ensure_ascii=False, indent=4)
|
||||
self.document_utf8 = document.encode('utf-8')
|
||||
|
||||
def on_get(self, req, resp, project_id):
|
||||
resp.data = self.document_utf8
|
||||
|
||||
resp.content_type = 'application/json-home'
|
||||
resp.cache_control = ['max-age=86400']
|
||||
# status defaults to 200
|
@ -1,300 +0,0 @@
|
||||
# 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 falcon
|
||||
from oslo_log import log as logging
|
||||
|
||||
from zaqar.common import decorators
|
||||
from zaqar.common.transport.wsgi import helpers as wsgi_helpers
|
||||
from zaqar.i18n import _
|
||||
from zaqar.storage import errors as storage_errors
|
||||
from zaqar.transport import utils
|
||||
from zaqar.transport import validation
|
||||
from zaqar.transport.wsgi import errors as wsgi_errors
|
||||
from zaqar.transport.wsgi import utils as wsgi_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
MESSAGE_POST_SPEC = (('ttl', int, None), ('body', '*', None))
|
||||
|
||||
|
||||
class CollectionResource(object):
|
||||
|
||||
__slots__ = ('_message_controller', '_wsgi_conf', '_validate')
|
||||
|
||||
def __init__(self, wsgi_conf, validate, message_controller):
|
||||
self._wsgi_conf = wsgi_conf
|
||||
self._validate = validate
|
||||
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:
|
||||
self._validate.message_listing(limit=len(ids))
|
||||
messages = self._message_controller.bulk_get(
|
||||
queue_name,
|
||||
message_ids=ids,
|
||||
project=project_id)
|
||||
|
||||
except validation.ValidationFailed as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPBadRequestAPI(str(ex))
|
||||
|
||||
except Exception:
|
||||
description = _('Message could not be retrieved.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
# Prepare response
|
||||
messages = list(messages)
|
||||
if not messages:
|
||||
return None
|
||||
|
||||
return [wsgi_utils.format_message_v1(m, base_path) for m in messages]
|
||||
|
||||
def _get(self, req, project_id, queue_name):
|
||||
client_uuid = wsgi_helpers.get_client_uuid(req)
|
||||
kwargs = {}
|
||||
|
||||
# NOTE(kgriffs): This syntax ensures that
|
||||
# we don't clobber default values with None.
|
||||
req.get_param('marker', store=kwargs)
|
||||
req.get_param_as_int('limit', store=kwargs)
|
||||
req.get_param_as_bool('echo', store=kwargs)
|
||||
req.get_param_as_bool('include_claimed', store=kwargs)
|
||||
|
||||
try:
|
||||
self._validate.message_listing(**kwargs)
|
||||
results = self._message_controller.list(
|
||||
queue_name,
|
||||
project=project_id,
|
||||
client_uuid=client_uuid,
|
||||
**kwargs)
|
||||
|
||||
# Buffer messages
|
||||
cursor = next(results)
|
||||
messages = list(cursor)
|
||||
|
||||
except validation.ValidationFailed as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPBadRequestAPI(str(ex))
|
||||
|
||||
except storage_errors.DoesNotExist as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPNotFound(str(ex))
|
||||
|
||||
except Exception:
|
||||
description = _('Messages could not be listed.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
if not messages:
|
||||
return None
|
||||
|
||||
# Found some messages, so prepare the response
|
||||
kwargs['marker'] = next(results)
|
||||
base_path = req.path.rsplit('/', 1)[0]
|
||||
messages = [wsgi_utils.format_message_v1(
|
||||
m, base_path) for m in messages]
|
||||
|
||||
return {
|
||||
'messages': messages,
|
||||
'links': [
|
||||
{
|
||||
'rel': 'next',
|
||||
'href': req.path + falcon.to_query_str(kwargs)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Interface
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
@decorators.TransportLog("Messages collection")
|
||||
def on_post(self, req, resp, project_id, queue_name):
|
||||
client_uuid = wsgi_helpers.get_client_uuid(req)
|
||||
|
||||
try:
|
||||
# Place JSON size restriction before parsing
|
||||
self._validate.message_length(req.content_length)
|
||||
except validation.ValidationFailed as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPBadRequestAPI(str(ex))
|
||||
|
||||
# Deserialize and validate the request body
|
||||
document = wsgi_utils.deserialize(req.stream, req.content_length)
|
||||
messages = wsgi_utils.sanitize(document, MESSAGE_POST_SPEC,
|
||||
doctype=wsgi_utils.JSONArray)
|
||||
|
||||
try:
|
||||
self._validate.message_posting(messages)
|
||||
|
||||
message_ids = self._message_controller.post(
|
||||
queue_name,
|
||||
messages=messages,
|
||||
project=project_id,
|
||||
client_uuid=client_uuid)
|
||||
|
||||
except validation.ValidationFailed as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPBadRequestAPI(str(ex))
|
||||
|
||||
except storage_errors.DoesNotExist as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPNotFound(str(ex))
|
||||
|
||||
except storage_errors.MessageConflict:
|
||||
description = _('No messages could be enqueued.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
except Exception:
|
||||
description = _('Messages could not be enqueued.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
# Prepare the response
|
||||
ids_value = ','.join(message_ids)
|
||||
resp.location = req.path + '?ids=' + ids_value
|
||||
|
||||
hrefs = [req.path + '/' + id for id in message_ids]
|
||||
|
||||
# NOTE(kgriffs): As of the Icehouse release, drivers are
|
||||
# no longer allowed to enqueue a subset of the messages
|
||||
# submitted by the client; it's all or nothing. Therefore,
|
||||
# 'partial' is now always False in the v1.0 API, and the
|
||||
# field has been removed in v1.1.
|
||||
body = {'resources': hrefs, 'partial': False}
|
||||
|
||||
resp.text = utils.to_json(body)
|
||||
resp.status = falcon.HTTP_201
|
||||
|
||||
@decorators.TransportLog("Messages collection")
|
||||
def on_get(self, req, resp, project_id, queue_name):
|
||||
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:
|
||||
response = self._get_by_id(req.path.rsplit('/', 1)[0], project_id,
|
||||
queue_name, ids)
|
||||
|
||||
if response is None:
|
||||
resp.status = falcon.HTTP_204
|
||||
return
|
||||
|
||||
resp.text = utils.to_json(response)
|
||||
# status defaults to 200
|
||||
|
||||
@decorators.TransportLog("Messages collection")
|
||||
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._validate.message_listing(limit=len(ids))
|
||||
self._message_controller.bulk_delete(
|
||||
queue_name,
|
||||
message_ids=ids,
|
||||
project=project_id)
|
||||
|
||||
except validation.ValidationFailed as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPBadRequestAPI(str(ex))
|
||||
|
||||
except Exception:
|
||||
description = _('Messages could not be deleted.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
resp.status = falcon.HTTP_204
|
||||
|
||||
|
||||
class ItemResource(object):
|
||||
|
||||
__slots__ = '_message_controller'
|
||||
|
||||
def __init__(self, message_controller):
|
||||
self._message_controller = message_controller
|
||||
|
||||
@decorators.TransportLog("Messages item")
|
||||
def on_get(self, req, resp, project_id, queue_name, message_id):
|
||||
try:
|
||||
message = self._message_controller.get(
|
||||
queue_name,
|
||||
message_id,
|
||||
project=project_id)
|
||||
|
||||
except storage_errors.DoesNotExist as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPNotFound(str(ex))
|
||||
|
||||
except Exception:
|
||||
description = _('Message could not be retrieved.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
resp.content_location = req.relative_uri
|
||||
message = wsgi_utils.format_message_v1(
|
||||
message, req.path.rsplit('/', 2)[0])
|
||||
resp.text = utils.to_json(message)
|
||||
# status defaults to 200
|
||||
|
||||
@decorators.TransportLog("Messages item")
|
||||
def on_delete(self, req, resp, project_id, queue_name, message_id):
|
||||
error_title = _('Unable to delete')
|
||||
|
||||
try:
|
||||
self._message_controller.delete(
|
||||
queue_name,
|
||||
message_id=message_id,
|
||||
project=project_id,
|
||||
claim=req.get_param('claim_id'))
|
||||
|
||||
except storage_errors.MessageNotClaimed as ex:
|
||||
LOG.debug(ex)
|
||||
description = _('A claim was specified, but the message '
|
||||
'is not currently claimed.')
|
||||
raise falcon.HTTPBadRequest(
|
||||
title=error_title, description=description)
|
||||
|
||||
except storage_errors.ClaimDoesNotExist as ex:
|
||||
LOG.debug(ex)
|
||||
description = _('The specified claim does not exist or '
|
||||
'has expired.')
|
||||
raise falcon.HTTPBadRequest(
|
||||
title=error_title, description=description)
|
||||
|
||||
except storage_errors.NotPermitted as ex:
|
||||
LOG.debug(ex)
|
||||
description = _('This message is claimed; it cannot be '
|
||||
'deleted without a valid claim ID.')
|
||||
raise falcon.HTTPForbidden(
|
||||
title=error_title, description=description)
|
||||
|
||||
except Exception:
|
||||
description = _('Message could not be deleted.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
# Alles guete
|
||||
resp.status = falcon.HTTP_204
|
@ -1,95 +0,0 @@
|
||||
# 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 falcon
|
||||
from oslo_log import log as logging
|
||||
|
||||
from zaqar.common import decorators
|
||||
from zaqar.i18n import _
|
||||
from zaqar.storage import errors as storage_errors
|
||||
from zaqar.transport import utils
|
||||
from zaqar.transport import validation
|
||||
from zaqar.transport.wsgi import errors as wsgi_errors
|
||||
from zaqar.transport.wsgi import utils as wsgi_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Resource(object):
|
||||
__slots__ = ('_wsgi_conf', '_validate', '_queue_ctrl')
|
||||
|
||||
def __init__(self, _wsgi_conf, validate, queue_controller):
|
||||
self._wsgi_conf = _wsgi_conf
|
||||
self._validate = validate
|
||||
self._queue_ctrl = queue_controller
|
||||
|
||||
@decorators.TransportLog("Queue metadata")
|
||||
def on_get(self, req, resp, project_id, queue_name):
|
||||
try:
|
||||
resp_dict = self._queue_ctrl.get_metadata(queue_name,
|
||||
project=project_id)
|
||||
|
||||
except storage_errors.DoesNotExist as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPNotFound(str(ex))
|
||||
|
||||
except Exception:
|
||||
description = _('Queue metadata could not be retrieved.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
resp.content_location = req.path
|
||||
resp.text = utils.to_json(resp_dict)
|
||||
# status defaults to 200
|
||||
|
||||
@decorators.TransportLog("Queue metadata")
|
||||
def on_put(self, req, resp, project_id, queue_name):
|
||||
try:
|
||||
# Place JSON size restriction before parsing
|
||||
self._validate.queue_metadata_length(req.content_length)
|
||||
# Deserialize queue metadata
|
||||
document = wsgi_utils.deserialize(req.stream, req.content_length)
|
||||
metadata = wsgi_utils.sanitize(document)
|
||||
# Restrict setting any reserved queue attributes
|
||||
for key in metadata:
|
||||
if key.startswith('_'):
|
||||
description = _('Reserved queue attributes in metadata '
|
||||
'(which names start with "_") can not be '
|
||||
'set in API v1.')
|
||||
raise validation.ValidationFailed(description)
|
||||
except validation.ValidationFailed as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPBadRequestAPI(str(ex))
|
||||
|
||||
try:
|
||||
self._queue_ctrl.set_metadata(queue_name,
|
||||
metadata=metadata,
|
||||
project=project_id)
|
||||
|
||||
except validation.ValidationFailed as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPBadRequestAPI(str(ex))
|
||||
|
||||
except storage_errors.QueueDoesNotExist as ex:
|
||||
raise wsgi_errors.HTTPNotFound(str(ex))
|
||||
|
||||
except Exception:
|
||||
description = _('Metadata could not be updated.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
resp.status = falcon.HTTP_204
|
||||
resp.location = req.path
|
@ -1,234 +0,0 @@
|
||||
# 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.
|
||||
|
||||
"""pools: a resource to handle storage pool management
|
||||
|
||||
A pool is added by an operator by interacting with the
|
||||
pooling-related endpoints. When specifying a pool, the
|
||||
following fields are required:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"name": string,
|
||||
"weight": integer,
|
||||
"uri": string::uri
|
||||
}
|
||||
|
||||
Furthermore, depending on the underlying storage type of pool being
|
||||
registered, there is an optional field::
|
||||
|
||||
{
|
||||
"options": {...}
|
||||
}
|
||||
"""
|
||||
|
||||
import falcon
|
||||
import jsonschema
|
||||
from oslo_log import log
|
||||
|
||||
from zaqar.common.api.schemas import pools as schema
|
||||
from zaqar.common import utils as common_utils
|
||||
from zaqar.storage import errors
|
||||
from zaqar.storage import utils as storage_utils
|
||||
from zaqar.transport import utils as transport_utils
|
||||
from zaqar.transport.wsgi import errors as wsgi_errors
|
||||
from zaqar.transport.wsgi import utils as wsgi_utils
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Listing(object):
|
||||
"""A resource to list registered pools
|
||||
|
||||
:param pools_controller: means to interact with storage
|
||||
"""
|
||||
|
||||
def __init__(self, pools_controller):
|
||||
self._ctrl = pools_controller
|
||||
|
||||
def on_get(self, request, response, project_id):
|
||||
"""Returns a pool listing as objects embedded in an object:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"pools": [
|
||||
{"href": "", "weight": 100, "uri": ""},
|
||||
...
|
||||
],
|
||||
"links": [
|
||||
{"href": "", "rel": "next"}
|
||||
]
|
||||
}
|
||||
|
||||
:returns: HTTP | 200
|
||||
"""
|
||||
|
||||
LOG.debug('LIST pools')
|
||||
|
||||
store = {}
|
||||
request.get_param('marker', store=store)
|
||||
request.get_param_as_int('limit', store=store)
|
||||
request.get_param_as_bool('detailed', store=store)
|
||||
|
||||
cursor = self._ctrl.list(**store)
|
||||
|
||||
pools = list(next(cursor))
|
||||
|
||||
results = {}
|
||||
|
||||
if pools:
|
||||
store['marker'] = next(cursor)
|
||||
|
||||
for entry in pools:
|
||||
entry['href'] = request.path + '/' + entry['name']
|
||||
|
||||
results['links'] = [
|
||||
{
|
||||
'rel': 'next',
|
||||
'href': request.path + falcon.to_query_str(store)
|
||||
}
|
||||
]
|
||||
results['pools'] = pools
|
||||
|
||||
response.content_location = request.relative_uri
|
||||
response.text = transport_utils.to_json(results)
|
||||
response.status = falcon.HTTP_200
|
||||
|
||||
|
||||
class Resource(object):
|
||||
"""A handler for individual pool.
|
||||
|
||||
:param pools_controller: means to interact with storage
|
||||
"""
|
||||
|
||||
def __init__(self, pools_controller):
|
||||
self._ctrl = pools_controller
|
||||
validator_type = jsonschema.Draft4Validator
|
||||
self._validators = {
|
||||
'weight': validator_type(schema.patch_weight),
|
||||
'uri': validator_type(schema.patch_uri),
|
||||
'options': validator_type(schema.patch_options),
|
||||
'create': validator_type(schema.create)
|
||||
}
|
||||
|
||||
def on_get(self, request, response, project_id, pool):
|
||||
"""Returns a JSON object for a single pool entry:
|
||||
|
||||
::
|
||||
|
||||
{"weight": 100, "uri": "", options: {...}}
|
||||
|
||||
:returns: HTTP | [200, 404]
|
||||
"""
|
||||
LOG.debug('GET pool - name: %s', pool)
|
||||
data = None
|
||||
detailed = request.get_param_as_bool('detailed') or False
|
||||
|
||||
try:
|
||||
data = self._ctrl.get(pool, detailed)
|
||||
|
||||
except errors.PoolDoesNotExist as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPNotFound(str(ex))
|
||||
|
||||
data['href'] = request.path
|
||||
|
||||
response.text = transport_utils.to_json(data)
|
||||
response.content_location = request.relative_uri
|
||||
|
||||
def on_put(self, request, response, project_id, pool):
|
||||
"""Registers a new pool. Expects the following input:
|
||||
|
||||
::
|
||||
|
||||
{"weight": 100, "uri": ""}
|
||||
|
||||
An options object may also be provided.
|
||||
|
||||
:returns: HTTP | [201, 204]
|
||||
"""
|
||||
|
||||
LOG.debug('PUT pool - name: %s', pool)
|
||||
|
||||
conf = self._ctrl.driver.conf
|
||||
data = wsgi_utils.load(request)
|
||||
wsgi_utils.validate(self._validators['create'], data)
|
||||
if not storage_utils.can_connect(data['uri'], conf=conf):
|
||||
raise wsgi_errors.HTTPBadRequestBody(
|
||||
'cannot connect to %s' % data['uri']
|
||||
)
|
||||
try:
|
||||
self._ctrl.create(pool, weight=data['weight'],
|
||||
uri=data['uri'],
|
||||
options=data.get('options', {}))
|
||||
response.status = falcon.HTTP_201
|
||||
response.location = request.path
|
||||
except errors.PoolAlreadyExists as e:
|
||||
LOG.exception('Pool "%s" already exists', pool)
|
||||
raise wsgi_errors.HTTPConflict(str(e))
|
||||
|
||||
def on_delete(self, request, response, project_id, pool):
|
||||
"""Deregisters a pool.
|
||||
|
||||
:returns: HTTP | 204
|
||||
"""
|
||||
|
||||
LOG.debug('DELETE pool - name: %s', pool)
|
||||
self._ctrl.delete(pool)
|
||||
response.status = falcon.HTTP_204
|
||||
|
||||
def on_patch(self, request, response, project_id, pool):
|
||||
"""Allows one to update a pool's weight, uri, and/or options.
|
||||
|
||||
This method expects the user to submit a JSON object
|
||||
containing at least one of: 'uri', 'weight', 'options'. If
|
||||
none are found, the request is flagged as bad. There is also
|
||||
strict format checking through the use of
|
||||
jsonschema. Appropriate errors are returned in each case for
|
||||
badly formatted input.
|
||||
|
||||
:returns: HTTP | 200,400
|
||||
"""
|
||||
|
||||
LOG.debug('PATCH pool - name: %s', pool)
|
||||
data = wsgi_utils.load(request)
|
||||
|
||||
EXPECT = ('weight', 'uri', 'options')
|
||||
if not any([(field in data) for field in EXPECT]):
|
||||
LOG.debug('PATCH pool, bad params')
|
||||
raise wsgi_errors.HTTPBadRequestBody(
|
||||
'One of `uri`, `weight`, or `options` needs '
|
||||
'to be specified'
|
||||
)
|
||||
|
||||
for field in EXPECT:
|
||||
wsgi_utils.validate(self._validators[field], data)
|
||||
|
||||
conf = self._ctrl.driver.conf
|
||||
if 'uri' in data and not storage_utils.can_connect(data['uri'],
|
||||
conf=conf):
|
||||
raise wsgi_errors.HTTPBadRequestBody(
|
||||
'cannot connect to %s' % data['uri']
|
||||
)
|
||||
fields = common_utils.fields(data, EXPECT,
|
||||
pred=lambda v: v is not None)
|
||||
|
||||
try:
|
||||
self._ctrl.update(pool, **fields)
|
||||
except errors.PoolDoesNotExist as ex:
|
||||
LOG.exception('Pool "%s" does not exist', pool)
|
||||
raise wsgi_errors.HTTPNotFound(str(ex))
|
@ -1,132 +0,0 @@
|
||||
# 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 falcon
|
||||
from oslo_log import log as logging
|
||||
|
||||
from zaqar.common import decorators
|
||||
from zaqar.i18n import _
|
||||
from zaqar.transport import utils
|
||||
from zaqar.transport import validation
|
||||
from zaqar.transport.wsgi import errors as wsgi_errors
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ItemResource(object):
|
||||
|
||||
__slots__ = ('_queue_controller', '_message_controller')
|
||||
|
||||
def __init__(self, queue_controller, message_controller):
|
||||
self._queue_controller = queue_controller
|
||||
self._message_controller = message_controller
|
||||
|
||||
@decorators.TransportLog("Queue item")
|
||||
def on_put(self, req, resp, project_id, queue_name):
|
||||
try:
|
||||
created = self._queue_controller.create(
|
||||
queue_name, project=project_id)
|
||||
|
||||
except Exception:
|
||||
description = _('Queue could not be created.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
resp.status = falcon.HTTP_201 if created else falcon.HTTP_204
|
||||
resp.location = req.path
|
||||
|
||||
@decorators.TransportLog("Queue item")
|
||||
def on_head(self, req, resp, project_id, queue_name):
|
||||
if self._queue_controller.exists(queue_name, project=project_id):
|
||||
resp.status = falcon.HTTP_204
|
||||
else:
|
||||
resp.status = falcon.HTTP_404
|
||||
|
||||
resp.content_location = req.path
|
||||
|
||||
on_get = on_head
|
||||
|
||||
@decorators.TransportLog("Queue item")
|
||||
def on_delete(self, req, resp, project_id, queue_name):
|
||||
try:
|
||||
self._queue_controller.delete(queue_name, project=project_id)
|
||||
|
||||
except Exception:
|
||||
description = _('Queue could not be deleted.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
resp.status = falcon.HTTP_204
|
||||
|
||||
|
||||
class CollectionResource(object):
|
||||
|
||||
__slots__ = ('_queue_controller', '_validate')
|
||||
|
||||
def __init__(self, validate, queue_controller):
|
||||
self._queue_controller = queue_controller
|
||||
self._validate = validate
|
||||
|
||||
def on_get(self, req, resp, project_id):
|
||||
LOG.debug('Queue collection GET')
|
||||
|
||||
kwargs = {}
|
||||
|
||||
# NOTE(kgriffs): This syntax ensures that
|
||||
# we don't clobber default values with None.
|
||||
req.get_param('marker', store=kwargs)
|
||||
req.get_param_as_int('limit', store=kwargs)
|
||||
req.get_param_as_bool('detailed', store=kwargs)
|
||||
|
||||
try:
|
||||
self._validate.queue_listing(**kwargs)
|
||||
results = self._queue_controller.list(project=project_id, **kwargs)
|
||||
|
||||
# Buffer list of queues
|
||||
queues = list(next(results))
|
||||
|
||||
except validation.ValidationFailed as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPBadRequestAPI(str(ex))
|
||||
|
||||
except Exception:
|
||||
description = _('Queues could not be listed.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
||||
|
||||
# Check for an empty list
|
||||
if len(queues) == 0:
|
||||
resp.status = falcon.HTTP_204
|
||||
return
|
||||
|
||||
# Got some. Prepare the response.
|
||||
kwargs['marker'] = next(results)
|
||||
for each_queue in queues:
|
||||
each_queue['href'] = req.path + '/' + each_queue['name']
|
||||
|
||||
response_body = {
|
||||
'queues': queues,
|
||||
'links': [
|
||||
{
|
||||
'rel': 'next',
|
||||
'href': req.path + falcon.to_query_str(kwargs)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
resp.content_location = req.relative_uri
|
||||
resp.text = utils.to_json(response_body)
|
||||
# status defaults to 200
|
@ -1,72 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from zaqar.i18n import _
|
||||
from zaqar.storage import errors as storage_errors
|
||||
from zaqar.transport import utils
|
||||
from zaqar.transport.wsgi import errors as wsgi_errors
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Resource(object):
|
||||
|
||||
__slots__ = '_queue_ctrl'
|
||||
|
||||
def __init__(self, queue_controller):
|
||||
self._queue_ctrl = queue_controller
|
||||
|
||||
def on_get(self, req, resp, project_id, queue_name):
|
||||
try:
|
||||
resp_dict = self._queue_ctrl.stats(queue_name,
|
||||
project=project_id)
|
||||
|
||||
message_stats = resp_dict['messages']
|
||||
|
||||
if message_stats['total'] != 0:
|
||||
base_path = req.path[:req.path.rindex('/')] + '/messages/'
|
||||
|
||||
newest = message_stats['newest']
|
||||
newest['href'] = base_path + newest['id']
|
||||
del newest['id']
|
||||
|
||||
oldest = message_stats['oldest']
|
||||
oldest['href'] = base_path + oldest['id']
|
||||
del oldest['id']
|
||||
|
||||
resp.content_location = req.path
|
||||
resp.text = utils.to_json(resp_dict)
|
||||
# status defaults to 200
|
||||
|
||||
except storage_errors.QueueIsEmpty:
|
||||
resp_dict = {
|
||||
'messages': {
|
||||
'claimed': 0,
|
||||
'free': 0,
|
||||
'total': 0
|
||||
}
|
||||
}
|
||||
resp.text = utils.to_json(resp_dict)
|
||||
except storage_errors.DoesNotExist as ex:
|
||||
LOG.debug(ex)
|
||||
raise wsgi_errors.HTTPNotFound(str(ex))
|
||||
|
||||
except Exception:
|
||||
description = _('Queue stats could not be read.')
|
||||
LOG.exception(description)
|
||||
raise wsgi_errors.HTTPServiceUnavailable(description)
|
@ -15,13 +15,11 @@
|
||||
import falcon
|
||||
|
||||
from zaqar.transport import utils
|
||||
from zaqar.transport.wsgi import v1_0
|
||||
from zaqar.transport.wsgi import v1_1
|
||||
from zaqar.transport.wsgi import v2_0
|
||||
|
||||
VERSIONS = {
|
||||
'versions': [
|
||||
v1_0.VERSION,
|
||||
v1_1.VERSION,
|
||||
v2_0.VERSION
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user