def add_node(self, node_list, lb_id, current_timestamp): """ Returns the canned response for add nodes """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, current_timestamp) if self.lbs[lb_id]["status"] != "ACTIVE": resource = invalid_resource( "Load Balancer '{0}' has a status of {1} and is considered " "immutable.".format(lb_id, self.lbs[lb_id]["status"]), 422) return (resource, 422) nodes = _format_nodes_on_lb(node_list) if self.lbs[lb_id].get("nodes"): for existing_node in self.lbs[lb_id]["nodes"]: for new_node in node_list: if (existing_node["address"] == new_node["address"] and existing_node["port"] == new_node["port"]): resource = invalid_resource( "Duplicate nodes detected. One or more nodes " "already configured on load balancer.", 413) return (resource, 413) self.lbs[lb_id]["nodes"] = self.lbs[lb_id]["nodes"] + nodes else: self.lbs[lb_id]["nodes"] = nodes self.lbs[lb_id]["nodeCount"] = len(self.lbs[lb_id]["nodes"]) _verify_and_update_lb_state(self, lb_id, current_timestamp=current_timestamp) return {"nodes": nodes}, 202 return not_found_response("loadbalancer"), 404
def delete_node(self, lb_id, node_id, current_timestamp): """ Determines whether the node to be deleted exists in the session store, deletes the node, and returns the response code. """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, current_timestamp) if self.lbs[lb_id]["status"] != "ACTIVE": # Error message verified as of 2015-04-22 resource = invalid_resource( "Load Balancer '{0}' has a status of '{1}' and is considered " "immutable.".format(lb_id, self.lbs[lb_id]["status"]), 422) return (resource, 422) _verify_and_update_lb_state(self, lb_id, current_timestamp=current_timestamp) if _delete_node(self, lb_id, node_id): return None, 202 else: return not_found_response("node"), 404 return not_found_response("loadbalancer"), 404
def del_load_balancer(self, lb_id, current_timestamp): """ Returns response for a load balancer is in building status for 20 seconds and response code 202, and adds the new lb to ``self.lbs``. A loadbalancer, on delete, goes into PENDING-DELETE and remains in DELETED status until a nightly job(maybe?) """ if lb_id in self.lbs: if self.lbs[lb_id]["status"] == "PENDING-DELETE": msg = ("Must provide valid load balancers: {0} are immutable and " "could not be processed.".format(lb_id)) # Dont doubt this to be 422, it is 400! return invalid_resource(msg, 400), 400 _verify_and_update_lb_state(self, lb_id, True, current_timestamp) if any([self.lbs[lb_id]["status"] == "ACTIVE", self.lbs[lb_id]["status"] == "ERROR", self.lbs[lb_id]["status"] == "PENDING-UPDATE"]): del self.lbs[lb_id] return EMPTY_RESPONSE, 202 if self.lbs[lb_id]["status"] == "PENDING-DELETE": return EMPTY_RESPONSE, 202 if self.lbs[lb_id]["status"] == "DELETED": _verify_and_update_lb_state(self, lb_id, current_timestamp=current_timestamp) msg = "Must provide valid load balancers: {0} could not be found.".format(lb_id) # Dont doubt this to be 422, it is 400! return invalid_resource(msg, 400), 400 return not_found_response("loadbalancer"), 404
def delete_node(self, lb_id, node_id): """ Determines whether the node to be deleted exists in the session store, deletes the node, and returns the response code. """ current_timestamp = self.clock.seconds() if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, current_timestamp) if self.lbs[lb_id]["status"] != "ACTIVE": # Error message verified as of 2015-04-22 return considered_immutable_error(self.lbs[lb_id]["status"], lb_id) _verify_and_update_lb_state(self, lb_id, current_timestamp=current_timestamp) if _delete_node(self, lb_id, node_id): return None, 202 else: return not_found_response("node"), 404 return not_found_response("loadbalancer"), 404
def get_load_balancers(self, lb_id): """ Returns the load balancers with the given lb id, with response code 200. If no load balancers are found returns 404. """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, self.clock.seconds()) log.msg(self.lbs[lb_id]["status"]) return {'loadBalancer': self.lbs[lb_id].full_json()}, 200 return not_found_response("loadbalancer"), 404
def get_load_balancers(self, lb_id, current_timestamp): """ Returns the load balancers with the given lb id, with response code 200. If no load balancers are found returns 404. """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, current_timestamp) log.msg(self.lbs[lb_id]["status"]) new_lb = _lb_without_tenant(self, lb_id) return {'loadBalancer': new_lb}, 200 return not_found_response("loadbalancer"), 404
def get_load_balancers(self, lb_id): """ Returns the load balancers with the given lb id, with response code 200. If no load balancers are found returns 404. """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, self.clock.seconds()) log.msg(self.lbs[lb_id]["status"]) return {'loadBalancer': self.lbs[lb_id].full_json()}, 200 return not_found_response("loadbalancer"), 404
def update_node(self, lb_id, node_id, node_updates): """ Update the weight, condition, or type of a single node. The IP, port, status, and ID are immutable, and attempting to change them will cause a 400 response to be returned. All success and error behavior verified as of 2016-06-16. :param str lb_id: the load balancer ID :param str node_id: the node ID to update :param dict node_updates: The JSON dictionary containing node attributes to update :param current_timestamp: What the current time is :return: a `tuple` of (json response as a dict, http status code) """ # first, store whether address and port were provided - if they were # that's a validation error not a schema error things_wrong = dict([(k, True) for k in ("address", "port", "id") if k in node_updates]) node_updates = dict([(k, v) for k, v in node_updates.items() if k not in ("address", "port")]) # use the Node.from_json to check the schema try: Node.from_json(dict(address="1.1.1.1", port=80, **node_updates)) except (TypeError, ValueError): return invalid_json_schema() # handle the possible validation (as opposed to schema) errors if not 1 <= node_updates.get('weight', 1) <= 100: things_wrong["weight"] = True if things_wrong: return updating_node_validation_error(**things_wrong) # Now, finally, check if the LB exists and node exists if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, self.clock.seconds()) if self.lbs[lb_id]["status"] != "ACTIVE": return considered_immutable_error(self.lbs[lb_id]["status"], lb_id) for i, node in enumerate(self.lbs[lb_id].nodes): if node.id == node_id: params = attr.asdict(node) params.update(node_updates) self.lbs[lb_id].nodes[i] = Node(**params) return ("", 202) return node_not_found() return loadbalancer_not_found()
def update_node(self, lb_id, node_id, node_updates): """ Update the weight, condition, or type of a single node. The IP, port, status, and ID are immutable, and attempting to change them will cause a 400 response to be returned. All success and error behavior verified as of 2016-06-16. :param str lb_id: the load balancer ID :param str node_id: the node ID to update :param dict node_updates: The JSON dictionary containing node attributes to update :param current_timestamp: What the current time is :return: a `tuple` of (json response as a dict, http status code) """ # first, store whether address and port were provided - if they were # that's a validation error not a schema error things_wrong = dict([(k, True) for k in ("address", "port", "id") if k in node_updates]) node_updates = dict([(k, v) for k, v in node_updates.items() if k not in ("address", "port")]) # use the Node.from_json to check the schema try: Node.from_json(dict(address="1.1.1.1", port=80, **node_updates)) except (TypeError, ValueError): return invalid_json_schema() # handle the possible validation (as opposed to schema) errors if not 1 <= node_updates.get('weight', 1) <= 100: things_wrong["weight"] = True if things_wrong: return updating_node_validation_error(**things_wrong) # Now, finally, check if the LB exists and node exists if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, self.clock.seconds()) if self.lbs[lb_id]["status"] != "ACTIVE": return considered_immutable_error( self.lbs[lb_id]["status"], lb_id) for i, node in enumerate(self.lbs[lb_id].nodes): if node.id == node_id: params = attr.asdict(node) params.update(node_updates) self.lbs[lb_id].nodes[i] = Node(**params) return ("", 202) return node_not_found() return loadbalancer_not_found()
def delete_nodes(self, lb_id, node_ids): """ Bulk-delete multiple LB nodes. """ if not node_ids: resp = { "message": "Must supply one or more id's to process this request.", "code": 400 } return resp, 400 if lb_id not in self.lbs: return not_found_response("loadbalancer"), 404 current_timestamp = self.clock.seconds() _verify_and_update_lb_state(self, lb_id, False, current_timestamp) if self.lbs[lb_id]["status"] != "ACTIVE": # Error message verified as of 2015-04-22 resp = {"message": "LoadBalancer is not ACTIVE", "code": 422} return resp, 422 # We need to verify all the deletions up front, and only allow it through # if all of them are valid. all_ids = [node.id for node in self.lbs[lb_id].nodes] non_nodes = set(node_ids).difference(all_ids) if non_nodes: nodes = ','.join(map(str, non_nodes)) resp = { "validationErrors": { "messages": [ "Node ids {0} are not a part of your loadbalancer". format(nodes) ] }, "message": "Validation Failure", "code": 400, "details": "The object is not valid" } return resp, 400 for node_id in node_ids: # It should not be possible for this to fail, since we've already # checked that they all exist. assert _delete_node(self, lb_id, node_id) is True _verify_and_update_lb_state(self, lb_id, current_timestamp=current_timestamp) return EMPTY_RESPONSE, 202
def list_load_balancers(self): """ Returns the list of load balancers with the given tenant id with response code 200. If no load balancers are found returns empty list. :return: A 2-tuple, containing the HTTP response and code, in that order. """ for each in self.lbs: _verify_and_update_lb_state(self, each, False, self.clock.seconds()) log.msg(self.lbs[each]["status"]) return ( {'loadBalancers': [lb.short_json() for lb in self.lbs.values()]}, 200)
def list_load_balancers(self): """ Returns the list of load balancers with the given tenant id with response code 200. If no load balancers are found returns empty list. :return: A 2-tuple, containing the HTTP response and code, in that order. """ for each in self.lbs: _verify_and_update_lb_state(self, each, False, self.clock.seconds()) log.msg(self.lbs[each]["status"]) return ({ 'loadBalancers': [lb.short_json() for lb in self.lbs.values()] }, 200)
def delete_nodes(self, lb_id, node_ids): """ Bulk-delete multiple LB nodes. """ if not node_ids: resp = { "message": "Must supply one or more id's to process this request.", "code": 400} return resp, 400 if lb_id not in self.lbs: return not_found_response("loadbalancer"), 404 current_timestamp = self.clock.seconds() _verify_and_update_lb_state(self, lb_id, False, current_timestamp) if self.lbs[lb_id]["status"] != "ACTIVE": # Error message verified as of 2015-04-22 resp = {"message": "LoadBalancer is not ACTIVE", "code": 422} return resp, 422 # We need to verify all the deletions up front, and only allow it through # if all of them are valid. all_ids = [node.id for node in self.lbs[lb_id].nodes] non_nodes = set(node_ids).difference(all_ids) if non_nodes: nodes = ','.join(map(str, non_nodes)) resp = { "validationErrors": { "messages": [ "Node ids {0} are not a part of your loadbalancer".format(nodes) ] }, "message": "Validation Failure", "code": 400, "details": "The object is not valid"} return resp, 400 for node_id in node_ids: # It should not be possible for this to fail, since we've already # checked that they all exist. assert _delete_node(self, lb_id, node_id) is True _verify_and_update_lb_state(self, lb_id, current_timestamp=current_timestamp) return EMPTY_RESPONSE, 202
def add_node(self, node_list, lb_id): """ Add one or more nodes to a load balancer. Fails if one or more of the nodes provided has the same address/port as an existing node. Also fails if adding the nodes would exceed the maximum number of nodes on the CLB. :param list node_list: a `list` of `dict` containing specification for nodes :return: a `tuple` of (json response as a dict, http status code) """ if lb_id in self.lbs: current_timestamp = self.clock.seconds() _verify_and_update_lb_state(self, lb_id, False, current_timestamp) if self.lbs[lb_id]["status"] != "ACTIVE": return considered_immutable_error(self.lbs[lb_id]["status"], lb_id) nodes = [Node.from_json(blob) for blob in node_list] for existing_node in self.lbs[lb_id].nodes: for new_node in nodes: if existing_node.same_as(new_node): resource = invalid_resource( "Duplicate nodes detected. One or more nodes " "already configured on load balancer.", 413) return (resource, 413) # If there were no duplicates new_nodeCount = len(self.lbs[lb_id].nodes) + len(nodes) if new_nodeCount <= self.node_limit: self.lbs[lb_id].nodes = self.lbs[lb_id].nodes + nodes else: resource = invalid_resource( "Nodes must not exceed {0} " "per load balancer.".format(self.node_limit), 413) return (resource, 413) _verify_and_update_lb_state(self, lb_id, current_timestamp=current_timestamp) return {"nodes": [node.as_json() for node in nodes]}, 202 return not_found_response("loadbalancer"), 404
def list_nodes(self, lb_id, current_timestamp): """ Returns the list of nodes remaining on the load balancer """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, current_timestamp) if lb_id not in self.lbs: return not_found_response("loadbalancer"), 404 if self.lbs[lb_id]["status"] == "DELETED": return invalid_resource("The loadbalancer is marked as deleted.", 410), 410 node_list = [] if self.lbs[lb_id].get("nodes"): node_list = self.lbs[lb_id]["nodes"] return {"nodes": node_list}, 200 else: return not_found_response("loadbalancer"), 404
def add_node(self, node_list, lb_id): """ Add one or more nodes to a load balancer. Fails if one or more of the nodes provided has the same address/port as an existing node. Also fails if adding the nodes would exceed the maximum number of nodes on the CLB. :param list node_list: a `list` of `dict` containing specification for nodes :return: a `tuple` of (json response as a dict, http status code) """ if lb_id in self.lbs: current_timestamp = self.clock.seconds() _verify_and_update_lb_state(self, lb_id, False, current_timestamp) if self.lbs[lb_id]["status"] != "ACTIVE": return considered_immutable_error( self.lbs[lb_id]["status"], lb_id) nodes = [Node.from_json(blob) for blob in node_list] for existing_node in self.lbs[lb_id].nodes: for new_node in nodes: if existing_node.same_as(new_node): resource = invalid_resource( "Duplicate nodes detected. One or more nodes " "already configured on load balancer.", 413) return (resource, 413) # If there were no duplicates new_nodeCount = len(self.lbs[lb_id].nodes) + len(nodes) if new_nodeCount <= self.node_limit: self.lbs[lb_id].nodes = self.lbs[lb_id].nodes + nodes else: resource = invalid_resource( "Nodes must not exceed {0} " "per load balancer.".format(self.node_limit), 413) return (resource, 413) _verify_and_update_lb_state(self, lb_id, current_timestamp=current_timestamp) return {"nodes": [node.as_json() for node in nodes]}, 202 return not_found_response("loadbalancer"), 404
def get_nodes(self, lb_id, node_id): """ Returns the node on the load balancer """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, self.clock.seconds()) if self.lbs[lb_id]["status"] == "DELETED": return (invalid_resource( "The loadbalancer is marked as deleted.", 410), 410) for each in self.lbs[lb_id].nodes: if node_id == each.id: return {"node": each.as_json()}, 200 return not_found_response("node"), 404 return not_found_response("loadbalancer"), 404
def list_nodes(self, lb_id): """ Returns the list of nodes remaining on the load balancer """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, self.clock.seconds()) if lb_id not in self.lbs: return not_found_response("loadbalancer"), 404 if self.lbs[lb_id]["status"] == "DELETED": return invalid_resource( "The loadbalancer is marked as deleted.", 410), 410 node_list = [node.as_json() for node in self.lbs[lb_id].nodes] return {"nodes": node_list}, 200 else: return not_found_response("loadbalancer"), 404
def list_nodes(self, lb_id): """ Returns the list of nodes remaining on the load balancer """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, self.clock.seconds()) if lb_id not in self.lbs: return not_found_response("loadbalancer"), 404 if self.lbs[lb_id]["status"] == "DELETED": return invalid_resource("The loadbalancer is marked as deleted.", 410), 410 node_list = [node.as_json() for node in self.lbs[lb_id].nodes] return {"nodes": node_list}, 200 else: return not_found_response("loadbalancer"), 404
def get_nodes(self, lb_id, node_id, current_timestamp): """ Returns the node on the load balancer """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, current_timestamp) if self.lbs[lb_id]["status"] == "DELETED": return ( invalid_resource( "The loadbalancer is marked as deleted.", 410), 410) if self.lbs[lb_id].get("nodes"): for each in self.lbs[lb_id]["nodes"]: if node_id == each["id"]: return {"node": each}, 200 return not_found_response("node"), 404 return not_found_response("loadbalancer"), 404
def list_load_balancers(self, tenant_id, current_timestamp): """ Returns the list of load balancers with the given tenant id with response code 200. If no load balancers are found returns empty list. :param string tenant_id: The tenant which owns the load balancers. :param float current_timestamp: The current time, in seconds since epoch. :return: A 2-tuple, containing the HTTP response and code, in that order. """ response = dict( (k, v) for (k, v) in self.lbs.items() if tenant_id == v['tenant_id'] ) for each in response: _verify_and_update_lb_state(self, each, False, current_timestamp) log.msg(self.lbs[each]["status"]) updated_resp = dict( (k, v) for (k, v) in self.lbs.items() if tenant_id == v['tenant_id'] ) return {'loadBalancers': _prep_for_list(updated_resp.values()) or []}, 200
def get_nodes(self, lb_id, node_id): """ Returns the node on the load balancer """ if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, self.clock.seconds()) if self.lbs[lb_id]["status"] == "DELETED": return ( invalid_resource( "The loadbalancer is marked as deleted.", 410), 410) for each in self.lbs[lb_id].nodes: if node_id == each.id: return {"node": each.as_json()}, 200 return not_found_response("node"), 404 return not_found_response("loadbalancer"), 404
def del_load_balancer(self, lb_id): """ Returns response for a load balancer is in building status for 20 seconds and response code 202, and adds the new lb to ``self.lbs``. A loadbalancer, on delete, goes into PENDING-DELETE and remains in DELETED status until a nightly job(maybe?) """ if lb_id in self.lbs: current_timestamp = self.clock.seconds() if self.lbs[lb_id]["status"] == "PENDING-DELETE": msg = ( "Must provide valid load balancers: {0} are immutable and " "could not be processed.".format(lb_id)) # Dont doubt this to be 422, it is 400! return invalid_resource(msg, 400), 400 _verify_and_update_lb_state(self, lb_id, True, current_timestamp) if any([ self.lbs[lb_id]["status"] == "ACTIVE", self.lbs[lb_id]["status"] == "ERROR", self.lbs[lb_id]["status"] == "PENDING-UPDATE" ]): del self.lbs[lb_id] return EMPTY_RESPONSE, 202 if self.lbs[lb_id]["status"] == "PENDING-DELETE": return EMPTY_RESPONSE, 202 if self.lbs[lb_id]["status"] == "DELETED": _verify_and_update_lb_state( self, lb_id, current_timestamp=current_timestamp) msg = "Must provide valid load balancers: {0} could not be found.".format( lb_id) # Dont doubt this to be 422, it is 400! return invalid_resource(msg, 400), 400 return not_found_response("loadbalancer"), 404
def delete_node(self, lb_id, node_id): """ Determines whether the node to be deleted exists in the session store, deletes the node, and returns the response code. """ current_timestamp = self.clock.seconds() if lb_id in self.lbs: _verify_and_update_lb_state(self, lb_id, False, current_timestamp) if self.lbs[lb_id]["status"] != "ACTIVE": # Error message verified as of 2015-04-22 return considered_immutable_error( self.lbs[lb_id]["status"], lb_id) _verify_and_update_lb_state(self, lb_id, current_timestamp=current_timestamp) if _delete_node(self, lb_id, node_id): return None, 202 else: return not_found_response("node"), 404 return not_found_response("loadbalancer"), 404