API+STATSD: Fixes

Statsd:
* Increase retry timeout to 30 seconds to work around weird worker pause

API:
* Add modify and node-modify support (and kludge to wsme to make it work)
* Fix status codes for wsme (currently requires trunk version of wsme)

Change-Id: Ib5f30b0d6306c4105fd30a6ca588a89ba96d29ee
This commit is contained in:
Andrew Hutchings 2013-06-05 17:45:27 +01:00
parent e7657309da
commit b8e7a919ba
5 changed files with 113 additions and 10 deletions

View File

@ -25,12 +25,15 @@ from virtualips import VipsController
# models # models
from libra.api.model.lbaas import LoadBalancer, Device, Node, session from libra.api.model.lbaas import LoadBalancer, Device, Node, session
from libra.api.model.lbaas import loadbalancers_devices, Limits from libra.api.model.lbaas import loadbalancers_devices, Limits
from libra.api.model.validators import LBPost, LBResp, LBVipResp, LBNode from libra.api.model.validators import LBPut, LBPost, LBResp, LBVipResp, LBNode
from libra.api.library.gearman_client import submit_job from libra.api.library.gearman_client import submit_job
from libra.api.acl import get_limited_to_project from libra.api.acl import get_limited_to_project
class LoadBalancersController(RestController): class LoadBalancersController(RestController):
def __init__(self, lbid=None):
self.lbid = lbid
@expose('json') @expose('json')
def get(self, load_balancer_id=None): def get(self, load_balancer_id=None):
"""Fetches a list of load balancers or the details of one balancer if """Fetches a list of load balancers or the details of one balancer if
@ -116,7 +119,7 @@ class LoadBalancersController(RestController):
response.status = 200 response.status = 200
return load_balancers return load_balancers
@wsme_pecan.wsexpose(LBResp, body=LBPost, status=202) @wsme_pecan.wsexpose(LBResp, body=LBPost, status_code=202)
def post(self, body=None): def post(self, body=None):
"""Accepts edit if load_balancer_id isn't blank or create load balancer """Accepts edit if load_balancer_id isn't blank or create load balancer
posts. posts.
@ -305,6 +308,40 @@ class LoadBalancersController(RestController):
session.rollback() session.rollback()
raise ClientSideError(errstr) raise ClientSideError(errstr)
@wsme_pecan.wsexpose(None, body=LBPut, status_code=202)
def put(self, body=None):
if not self.lbid:
raise ClientSideError('Load Balancer ID is required')
tenant_id = get_limited_to_project(request.headers)
# grab the lb
lb = session.query(LoadBalancer).\
filter(LoadBalancer.id == self.lbid).\
filter(LoadBalancer.tenantid == tenant_id).\
filter(LoadBalancer.status != 'DELETED').first()
if lb is None:
raise ClientSideError('Load Balancer ID is not valid')
if body.name != Unset:
lb.name = body.name
if body.algorithm != Unset:
lb.algorithm = body.algorithm
lb.status = 'PENDING_UPDATE'
device = session.query(
Device.id, Device.name
).join(LoadBalancer.devices).\
filter(LoadBalancer.id == self.lbid).\
first()
session.commit()
submit_job(
'UPDATE', device.name, device.id, lb.id
)
return
@expose('json') @expose('json')
def delete(self, load_balancer_id): def delete(self, load_balancer_id):
"""Remove a load balancer from the account. """Remove a load balancer from the account.
@ -386,5 +423,7 @@ class LoadBalancersController(RestController):
return NodesController(lbid), remainder[1:] return NodesController(lbid), remainder[1:]
if remainder[0] == 'virtualips': if remainder[0] == 'virtualips':
return VipsController(lbid), remainder[1:] return VipsController(lbid), remainder[1:]
# Kludgy fix for PUT since WSME doesn't like IDs on the path
elif lbid:
return LoadBalancersController(lbid), remainder
abort(404) abort(404)

View File

@ -13,21 +13,24 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from pecan import expose, response, request from pecan import expose, response, request, abort
from pecan.rest import RestController from pecan.rest import RestController
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from wsme.exc import ClientSideError from wsme.exc import ClientSideError
from wsme import Unset
#default response objects #default response objects
from libra.api.model.lbaas import LoadBalancer, Node, session, Limits, Device from libra.api.model.lbaas import LoadBalancer, Node, session, Limits, Device
from libra.api.acl import get_limited_to_project from libra.api.acl import get_limited_to_project
from libra.api.model.validators import LBNodeResp, LBNodePost, NodeResp 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.gearman_client import submit_job
class NodesController(RestController): class NodesController(RestController):
"""Functions for /loadbalancers/{load_balancer_id}/nodes/* routing""" """Functions for /loadbalancers/{load_balancer_id}/nodes/* routing"""
def __init__(self, lbid): def __init__(self, lbid, nodeid=None):
self.lbid = lbid self.lbid = lbid
self.nodeid = nodeid
@expose('json') @expose('json')
def get(self, node_id=None): def get(self, node_id=None):
@ -89,7 +92,7 @@ class NodesController(RestController):
response.status = 200 response.status = 200
return node_response return node_response
@wsme_pecan.wsexpose(LBNodeResp, body=LBNodePost, status=202) @wsme_pecan.wsexpose(LBNodeResp, body=LBNodePost, status_code=202)
def post(self, body=None): def post(self, body=None):
"""Adds a new node to the load balancer OR Modify the configuration """Adds a new node to the load balancer OR Modify the configuration
of a node on the load balancer. of a node on the load balancer.
@ -166,6 +169,45 @@ class NodesController(RestController):
) )
return return_data return return_data
@wsme_pecan.wsexpose(None, body=LBNodePut, status_code=202)
def put(self, body=None):
if not self.lbid:
raise ClientSideError('Load Balancer ID has not been supplied')
if not self.nodeid:
raise ClientSideError('Node ID has not been supplied')
tenant_id = get_limited_to_project(request.headers)
# grab the lb
lb = session.query(LoadBalancer).\
filter(LoadBalancer.id == self.lbid).\
filter(LoadBalancer.tenantid == tenant_id).\
filter(LoadBalancer.status != 'DELETED').first()
if lb is None:
raise ClientSideError('Load Balancer ID is not valid')
node = session.query(Node).\
filter(Node.lbid == self.lbid).\
filter(Node.id == self.nodeid).first()
if node is None:
raise ClientSideError('Node ID is not valid')
if body.condition != Unset:
node.condition = body.condition
lb.status = 'PENDING_UPDATE'
device = session.query(
Device.id, Device.name
).join(LoadBalancer.devices).\
filter(LoadBalancer.id == self.lbid).\
first()
session.commit()
submit_job(
'UPDATE', device.name, device.id, lb.id
)
return
@expose('json') @expose('json')
def delete(self, node_id): def delete(self, node_id):
"""Remove a node from the load balancer. """Remove a node from the load balancer.
@ -230,3 +272,14 @@ class NodesController(RestController):
) )
response.status = 202 response.status = 202
return None return None
@expose('json')
def _lookup(self, nodeid, *remainder):
"""Routes more complex url mapping.
Raises: 404
"""
# Kludgy fix for PUT since WSME doesn't like IDs on the path
if nodeid:
return NodesController(self.lbid, nodeid), remainder
abort(404)

View File

@ -74,6 +74,7 @@ class GearmanClientThread(object):
'loadBalancers': [{ 'loadBalancers': [{
'name': keep_lb.name, 'name': keep_lb.name,
'protocol': keep_lb.protocol, 'protocol': keep_lb.protocol,
'algorithm': keep_lb.algorithm,
'port': keep_lb.port, 'port': keep_lb.port,
'nodes': [] 'nodes': []
}] }]
@ -139,6 +140,7 @@ class GearmanClientThread(object):
lb_data = { lb_data = {
'name': lb.name, 'name': lb.name,
'protocol': lb.protocol, 'protocol': lb.protocol,
'algorithm': lb.algorithm,
'port': lb.port, 'port': lb.port,
'nodes': [] 'nodes': []
} }

View File

@ -15,13 +15,17 @@
from wsme import types as wtypes from wsme import types as wtypes
from wsme import wsattr from wsme import wsattr
from wsme.types import Base from wsme.types import Base, Enum
class LBNode(Base): class LBNode(Base):
port = wsattr(int, mandatory=True) port = wsattr(int, mandatory=True)
address = wsattr(wtypes.text, mandatory=True) address = wsattr(wtypes.text, mandatory=True)
condition = wtypes.text condition = Enum(wtypes.text, 'ENABLED', 'DISABLED')
class LBNodePut(Base):
condition = Enum(wtypes.text, 'ENABLED', 'DISABLED')
class NodeResp(Base): class NodeResp(Base):
@ -48,11 +52,16 @@ class LBPost(Base):
name = wsattr(wtypes.text, mandatory=True) name = wsattr(wtypes.text, mandatory=True)
nodes = wsattr(['LBNode'], mandatory=True) nodes = wsattr(['LBNode'], mandatory=True)
protocol = wtypes.text protocol = wtypes.text
algorithm = wtypes.text algorithm = Enum(wtypes.text, 'ROUND_ROBIN', 'LEAST_CONNECTIONS')
port = int port = int
virtualIps = wsattr(['LBVip']) virtualIps = wsattr(['LBVip'])
class LBPut(Base):
name = wtypes.text
algorithm = Enum(wtypes.text, 'ROUND_ROBIN', 'LEAST_CONNECTIONS')
class LBVipResp(Base): class LBVipResp(Base):
id = int id = int
address = wtypes.text address = wtypes.text

View File

@ -60,7 +60,7 @@ class GearJobs(object):
list_of_jobs.append(dict(task=str(node), data=job_data)) list_of_jobs.append(dict(task=str(node), data=job_data))
submitted_pings = self.gm_client.submit_multiple_jobs( submitted_pings = self.gm_client.submit_multiple_jobs(
list_of_jobs, background=False, wait_until_complete=True, list_of_jobs, background=False, wait_until_complete=True,
poll_timeout=10.0 poll_timeout=30.0
) )
for ping in submitted_pings: for ping in submitted_pings:
if ping.state == 'UNKNOWN': if ping.state == 'UNKNOWN':