Merge "API: fixes and error handling"

This commit is contained in:
Jenkins 2013-06-07 20:46:50 +00:00 committed by Gerrit Code Review
commit 0c506e7a2c
8 changed files with 249 additions and 29 deletions

View File

@ -21,6 +21,7 @@ import pwd
import pecan
import sys
import os
import wsme_overrides
from libra.api import config as api_config
from libra.api import model
from libra.api import acl
@ -28,6 +29,10 @@ from libra.common.options import Options, setup_logging
from eventlet import wsgi
# Gets rid of pep8 error
assert wsme_overrides
def get_pecan_config():
# Set up the pecan configuration
filename = api_config.__file__.replace('.pyc', '.py')

View File

@ -26,9 +26,9 @@ app = {
}
}
wsme = {
'debug': True
}
#wsme = {
# 'debug': True
#}
#database = {
# 'username': 'root',

View File

@ -13,6 +13,7 @@
# under the License.
import logging
import socket
# pecan imports
from pecan import expose, abort, response, request
from pecan.rest import RestController
@ -26,9 +27,11 @@ from logs import LogsController
# models
from libra.api.model.lbaas import LoadBalancer, Device, Node, session
from libra.api.model.lbaas import loadbalancers_devices, Limits
from libra.api.model.validators import LBPut, LBPost, LBResp, LBVipResp, LBNode
from libra.api.model.validators import LBPut, LBPost, LBResp, LBVipResp
from libra.api.model.validators import LBRespNode
from libra.api.library.gearman_client import submit_job
from libra.api.acl import get_limited_to_project
from libra.api.library.exp import OverLimit
class LoadBalancersController(RestController):
@ -58,13 +61,20 @@ class LoadBalancersController(RestController):
# if we don't have an id then we want a list of them own by this tenent
if not load_balancer_id:
load_balancers = {'loadBalancers': session.query(
lbs = session.query(
LoadBalancer.name, LoadBalancer.id, LoadBalancer.protocol,
LoadBalancer.port, LoadBalancer.algorithm,
LoadBalancer.status, LoadBalancer.created,
LoadBalancer.updated
).filter(LoadBalancer.tenantid == tenant_id).
filter(LoadBalancer.status != 'DELETED').all()}
).filter(LoadBalancer.tenantid == tenant_id).\
filter(LoadBalancer.status != 'DELETED').all()
load_balancers = {'loadBalancers': []}
for lb in lbs:
lb = lb._asdict()
lb['id'] = str(lb['id'])
load_balancers['loadBalancers'].append(lb)
else:
load_balancers = session.query(
LoadBalancer.name, LoadBalancer.id, LoadBalancer.protocol,
@ -97,6 +107,8 @@ class LoadBalancersController(RestController):
vip = item._asdict()
vip['type'] = 'PUBLIC'
vip['ipVersion'] = 'IPV4'
vip['address'] = vip['floatingIpAddr']
del(vip['floatingIpAddr'])
load_balancers['virtualIps'].append(vip)
nodes = session.query(
@ -106,6 +118,10 @@ class LoadBalancersController(RestController):
filter(LoadBalancer.id == load_balancer_id).\
all()
load_balancers['id'] = str(load_balancers['id'])
if not load_balancers['statusDescription']:
load_balancers['statusDescription'] = ''
load_balancers['nodes'] = []
for item in nodes:
node = item._asdict()
@ -114,9 +130,11 @@ class LoadBalancersController(RestController):
else:
node['condition'] = 'DISABLED'
del node['enabled']
node['port'] = str(node['port'])
node['id'] = str(node['id'])
load_balancers['nodes'].append(node)
session.commit()
session.rollback()
response.status = 200
return load_balancers
@ -140,27 +158,48 @@ class LoadBalancersController(RestController):
Returns: dict
"""
tenant_id = get_limited_to_project(request.headers)
if body.nodes == Unset:
if body.nodes == Unset or not len(body.nodes):
raise ClientSideError(
'At least one backend node needs to be supplied'
)
for node in body.nodes:
if node.address == Unset:
raise ClientSideError(
'A supplied node has no address'
)
if node.port == Unset:
raise ClientSideError(
'Node {0} is missing a port'.format(node.address)
)
try:
socket.inet_aton(node.address)
except socket.error:
raise ClientSideError(
'IP Address {0} not valid'.format(node.address)
)
lblimit = session.query(Limits.value).\
filter(Limits.name == 'maxLoadBalancers').scalar()
nodelimit = session.query(Limits.value).\
filter(Limits.name == 'maxNodesPerLoadBalancer').scalar()
namelimit = session.query(Limits.value).\
filter(Limits.name == 'maxLoadBalancerNameLength').scalar()
count = session.query(LoadBalancer).\
filter(LoadBalancer.tenantid == tenant_id).\
filter(LoadBalancer.status != 'DELETED').count()
if len(body.name) > namelimit:
raise ClientSideError(
'Length of Load Balancer name too long'
)
# TODO: this should probably be a 413, not sure how to do that yet
if count >= lblimit:
raise ClientSideError(
raise OverLimit(
'Account has hit limit of {0} Load Balancers'.
format(lblimit)
)
if len(body.nodes) > nodelimit:
raise ClientSideError(
raise OverLimit(
'Too many backend nodes supplied (limit is {0}'.
format(nodelimit)
)
@ -261,7 +300,7 @@ class LoadBalancersController(RestController):
enabled = 1
out_node = Node(
lbid=lb.id, port=node.port, address=node.address,
enabled=enabled, status='ONLINE', weight=0
enabled=enabled, status='ONLINE', weight=1
)
session.add(out_node)
@ -273,23 +312,23 @@ class LoadBalancersController(RestController):
try:
return_data = LBResp()
return_data.id = lb.id
return_data.id = str(lb.id)
return_data.name = lb.name
return_data.protocol = lb.protocol
return_data.port = lb.port
return_data.port = str(lb.port)
return_data.algorithm = lb.algorithm
return_data.status = lb.status
return_data.created = lb.created
return_data.updated = lb.updated
vip_resp = LBVipResp(
address=device.floatingIpAddr, id=device.id,
address=device.floatingIpAddr, id=str(device.id),
type='PUBLIC', ipVersion='IPV4'
)
return_data.virtualIps = [vip_resp]
return_data.nodes = []
for node in body.nodes:
out_node = LBNode(
port=node.port, address=node.address,
out_node = LBRespNode(
port=str(node.port), address=node.address,
condition=node.condition
)
return_data.nodes.append(out_node)
@ -326,6 +365,12 @@ class LoadBalancersController(RestController):
raise ClientSideError('Load Balancer ID is not valid')
if body.name != Unset:
namelimit = session.query(Limits.value).\
filter(Limits.name == 'maxLoadBalancerNameLength').scalar()
if len(body.name) > namelimit:
raise ClientSideError(
'Length of Load Balancer name too long'
)
lb.name = body.name
if body.algorithm != Unset:
@ -341,7 +386,7 @@ class LoadBalancersController(RestController):
submit_job(
'UPDATE', device.name, device.id, lb.id
)
return
return ''
@expose('json')
def delete(self, load_balancer_id):
@ -388,7 +433,7 @@ class LoadBalancersController(RestController):
'DELETE', device.name, device.id, lb.id
)
response.status = 202
return None
return ''
except:
logger = logging.getLogger(__name__)
logger.exception('Error communicating with load balancer pool')

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import socket
from pecan import expose, response, request, abort
from pecan.rest import RestController
import wsmeext.pecan as wsme_pecan
@ -24,6 +25,7 @@ from libra.api.acl import get_limited_to_project
from libra.api.model.validators import LBNodeResp, LBNodePost, NodeResp
from libra.api.model.validators import LBNodePut
from libra.api.library.gearman_client import submit_job
from libra.api.library.exp import OverLimit
class NodesController(RestController):
@ -112,9 +114,25 @@ class NodesController(RestController):
if self.lbid is None:
raise ClientSideError('Load Balancer ID has not been supplied')
if not len(body.nodes):
if body.nodes == Unset or not len(body.nodes):
raise ClientSideError('No nodes have been supplied')
for node in body.nodes:
if node.address == Unset:
raise ClientSideError(
'A supplied node has no address'
)
if node.port == Unset:
raise ClientSideError(
'Node {0} is missing a port'.format(node.address)
)
try:
socket.inet_aton(node.address)
except socket.error:
raise ClientSideError(
'IP Address {0} not valid'.format(node.address)
)
load_balancer = session.query(LoadBalancer).\
filter(LoadBalancer.tenantid == tenant_id).\
filter(LoadBalancer.id == self.lbid).\
@ -130,8 +148,8 @@ class NodesController(RestController):
nodecount = session.query(Node).\
filter(Node.lbid == self.lbid).count()
if (nodecount + len(body.nodes)) >= nodelimit:
raise ClientSideError(
if (nodecount + len(body.nodes)) > nodelimit:
raise OverLimit(
'Command would exceed Load Balancer node limit'
)
return_data = LBNodeResp()
@ -143,7 +161,7 @@ class NodesController(RestController):
enabled = 1
new_node = Node(
lbid=self.lbid, port=node.port, address=node.address,
enabled=enabled, status='ONLINE', weight=0
enabled=enabled, status='ONLINE', weight=1
)
session.add(new_node)
session.flush()
@ -206,7 +224,7 @@ class NodesController(RestController):
submit_job(
'UPDATE', device.name, device.id, lb.id
)
return
return ''
@expose('json')
def delete(self, node_id):

27
libra/api/library/exp.py Normal file
View File

@ -0,0 +1,27 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
from wsme.exc import ClientSideError
from wsme.utils import _
class OverLimit(ClientSideError):
def __init__(self, msg=''):
self.msg = msg
super(OverLimit, self).__init__()
@property
def faultstring(self):
return _(six.u("OverLimit: %s")) % (self.msg)

View File

@ -107,7 +107,8 @@ class GearmanClientThread(object):
# Device should never be used again
device = session.query(Device).\
filter(Device.id == data).first()
device.status = 'DELETED'
#TODO: change this to 'DELETED' when pool mgm deletes
device.status = 'OFFLINE'
session.commit()
def _set_error(self, device_id, errmsg):

View File

@ -24,6 +24,12 @@ class LBNode(Base):
condition = Enum(wtypes.text, 'ENABLED', 'DISABLED')
class LBRespNode(Base):
port = wtypes.text
address = wtypes.text
condition = wtypes.text
class LBNodePut(Base):
condition = Enum(wtypes.text, 'ENABLED', 'DISABLED')
@ -63,7 +69,7 @@ class LBPut(Base):
class LBVipResp(Base):
id = int
id = wtypes.text
address = wtypes.text
type = wtypes.text
ipVersion = wtypes.text
@ -77,13 +83,13 @@ class LBLogsPost(Base):
class LBResp(Base):
id = int
id = wtypes.text
name = wtypes.text
protocol = wtypes.text
port = int
port = wtypes.text
algorithm = wtypes.text
status = wtypes.text
created = wtypes.text
updated = wtypes.text
virtualIps = wsattr(['LBVipResp'])
nodes = wsattr(['LBNode'])
nodes = wsattr(['LBRespNode'])

118
libra/api/wsme_overrides.py Normal file
View File

@ -0,0 +1,118 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# 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 logging
import traceback
import functools
import inspect
import sys
import wsme
import wsme.rest.args
import wsme.rest.json
import wsme.rest.xml
import wsmeext.pecan
import pecan
from libra.api.library.exp import OverLimit
def format_exception(excinfo, debug=False):
"""Extract informations that can be sent to the client."""
error = excinfo[1]
log = logging.getLogger(__name__)
if isinstance(error, wsme.exc.ClientSideError):
r = dict(faultcode="Client",
faultstring=error.faultstring)
log.warning("Client-side error: %s" % r['faultstring'])
return r
else:
faultstring = str(error)
debuginfo = "\n".join(traceback.format_exception(*excinfo))
log.error('Server-side error: "%s". Detail: \n%s' % (
faultstring, debuginfo))
if isinstance(error, ValueError):
r = dict(faultcode="Client", faultstring=faultstring)
else:
r = dict(faultcode="Server", faultstring=faultstring)
if debug:
r['debuginfo'] = debuginfo
return r
wsme.api.format_exception = format_exception
def wsexpose(*args, **kwargs):
pecan_json_decorate = pecan.expose(
template='wsmejson:',
content_type='application/json',
generic=False)
pecan_xml_decorate = pecan.expose(
template='wsmexml:',
content_type='application/xml',
generic=False
)
sig = wsme.signature(*args, **kwargs)
def decorate(f):
sig(f)
funcdef = wsme.api.FunctionDefinition.get(f)
funcdef.resolve_types(wsme.types.registry)
@functools.wraps(f)
def callfunction(self, *args, **kwargs):
try:
args, kwargs = wsme.rest.args.get_args(
funcdef, args, kwargs, pecan.request.params, None,
pecan.request.body, pecan.request.content_type
)
if funcdef.pass_request:
kwargs[funcdef.pass_request] = pecan.request
result = f(self, *args, **kwargs)
# NOTE: Support setting of status_code with default 201
pecan.response.status = funcdef.status_code
if isinstance(result, wsme.api.Response):
pecan.response.status = result.status_code
result = result.obj
except:
data = wsme.api.format_exception(
sys.exc_info(),
pecan.conf.get('wsme', {}).get('debug', False)
)
e = sys.exc_info()[1]
if isinstance(e, OverLimit):
pecan.response.status = 413
elif data['faultcode'] == 'Client':
pecan.response.status = 400
else:
pecan.response.status = 500
return data
return dict(
datatype=funcdef.return_type,
result=result
)
pecan_xml_decorate(callfunction)
pecan_json_decorate(callfunction)
pecan.util._cfg(callfunction)['argspec'] = inspect.getargspec(f)
callfunction._wsme_definition = funcdef
return callfunction
return decorate
wsmeext.pecan.wsexpose = wsexpose