diff --git a/libra/api/controllers/load_balancers.py b/libra/api/controllers/load_balancers.py index d4f60926..d6fe34d5 100644 --- a/libra/api/controllers/load_balancers.py +++ b/libra/api/controllers/load_balancers.py @@ -25,12 +25,15 @@ from virtualips import VipsController # 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 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.acl import get_limited_to_project class LoadBalancersController(RestController): + def __init__(self, lbid=None): + self.lbid = lbid + @expose('json') def get(self, load_balancer_id=None): """Fetches a list of load balancers or the details of one balancer if @@ -116,7 +119,7 @@ class LoadBalancersController(RestController): response.status = 200 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): """Accepts edit if load_balancer_id isn't blank or create load balancer posts. @@ -305,6 +308,40 @@ class LoadBalancersController(RestController): session.rollback() 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') def delete(self, load_balancer_id): """Remove a load balancer from the account. @@ -386,5 +423,7 @@ class LoadBalancersController(RestController): return NodesController(lbid), remainder[1:] if remainder[0] == 'virtualips': 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) diff --git a/libra/api/controllers/nodes.py b/libra/api/controllers/nodes.py index dd69fbf5..3a551b85 100644 --- a/libra/api/controllers/nodes.py +++ b/libra/api/controllers/nodes.py @@ -13,21 +13,24 @@ # License for the specific language governing permissions and limitations # under the License. -from pecan import expose, response, request +from pecan import expose, response, request, abort from pecan.rest import RestController import wsmeext.pecan as wsme_pecan from wsme.exc import ClientSideError +from wsme import Unset #default response objects from libra.api.model.lbaas import LoadBalancer, Node, session, Limits, Device 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 class NodesController(RestController): """Functions for /loadbalancers/{load_balancer_id}/nodes/* routing""" - def __init__(self, lbid): + def __init__(self, lbid, nodeid=None): self.lbid = lbid + self.nodeid = nodeid @expose('json') def get(self, node_id=None): @@ -89,7 +92,7 @@ class NodesController(RestController): response.status = 200 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): """Adds a new node to the load balancer OR Modify the configuration of a node on the load balancer. @@ -166,6 +169,45 @@ class NodesController(RestController): ) 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') def delete(self, node_id): """Remove a node from the load balancer. @@ -230,3 +272,14 @@ class NodesController(RestController): ) response.status = 202 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) diff --git a/libra/api/library/gearman_client.py b/libra/api/library/gearman_client.py index 9bf02aef..287f3f31 100644 --- a/libra/api/library/gearman_client.py +++ b/libra/api/library/gearman_client.py @@ -74,6 +74,7 @@ class GearmanClientThread(object): 'loadBalancers': [{ 'name': keep_lb.name, 'protocol': keep_lb.protocol, + 'algorithm': keep_lb.algorithm, 'port': keep_lb.port, 'nodes': [] }] @@ -139,6 +140,7 @@ class GearmanClientThread(object): lb_data = { 'name': lb.name, 'protocol': lb.protocol, + 'algorithm': lb.algorithm, 'port': lb.port, 'nodes': [] } diff --git a/libra/api/model/validators.py b/libra/api/model/validators.py index c41776ca..ffb2a2b2 100644 --- a/libra/api/model/validators.py +++ b/libra/api/model/validators.py @@ -15,13 +15,17 @@ from wsme import types as wtypes from wsme import wsattr -from wsme.types import Base +from wsme.types import Base, Enum class LBNode(Base): port = wsattr(int, 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): @@ -48,11 +52,16 @@ class LBPost(Base): name = wsattr(wtypes.text, mandatory=True) nodes = wsattr(['LBNode'], mandatory=True) protocol = wtypes.text - algorithm = wtypes.text + algorithm = Enum(wtypes.text, 'ROUND_ROBIN', 'LEAST_CONNECTIONS') port = int virtualIps = wsattr(['LBVip']) +class LBPut(Base): + name = wtypes.text + algorithm = Enum(wtypes.text, 'ROUND_ROBIN', 'LEAST_CONNECTIONS') + + class LBVipResp(Base): id = int address = wtypes.text diff --git a/libra/statsd/gearman.py b/libra/statsd/gearman.py index df269d4e..7d7ebb4f 100644 --- a/libra/statsd/gearman.py +++ b/libra/statsd/gearman.py @@ -60,7 +60,7 @@ class GearJobs(object): list_of_jobs.append(dict(task=str(node), data=job_data)) submitted_pings = self.gm_client.submit_multiple_jobs( list_of_jobs, background=False, wait_until_complete=True, - poll_timeout=10.0 + poll_timeout=30.0 ) for ping in submitted_pings: if ping.state == 'UNKNOWN':