package org.openstack.atlas.service.domain.services.impl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openstack.atlas.service.domain.deadlock.DeadLockRetry; import org.openstack.atlas.service.domain.entities.*; import org.openstack.atlas.service.domain.exceptions.*; import org.openstack.atlas.service.domain.pojos.NodeMap; import org.openstack.atlas.service.domain.services.AccountLimitService; import org.openstack.atlas.service.domain.services.LoadBalancerService; import org.openstack.atlas.service.domain.services.LoadBalancerStatusHistoryService; import org.openstack.atlas.service.domain.services.NodeService; import org.openstack.atlas.service.domain.services.helpers.NodesHelper; import org.openstack.atlas.service.domain.services.helpers.NodesPrioritiesContainer; import org.openstack.atlas.service.domain.services.helpers.StringHelper; import org.openstack.atlas.service.domain.util.Constants; import org.openstack.atlas.util.converters.StringConverter; import org.openstack.atlas.util.ip.IPUtils; import org.openstack.atlas.util.ip.IPv6; import org.openstack.atlas.util.ip.exception.IPStringConversionException; import org.openstack.atlas.util.ip.exception.IpTypeMissMatchException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import java.util.*; @Service public class NodeServiceImpl extends BaseService implements NodeService { private final Log LOG = LogFactory.getLog(NodeServiceImpl.class); @Autowired private AccountLimitService accountLimitService; @Autowired private LoadBalancerService loadBalancerService; @Autowired private LoadBalancerStatusHistoryService loadBalancerStatusHistoryService; @Override @Transactional public Set<Node> getNodesByAccountIdLoadBalancerId(Integer accountId, Integer loadbalancerId, Integer... p) throws EntityNotFoundException, DeletedStatusException { Set<Node> nodes; nodes = nodeRepository.getNodesByAccountIdLoadBalancerId(loadBalancerRepository.getByIdAndAccountId(loadbalancerId, accountId), p); return nodes; } @Override @Transactional public Set<Node> getAllNodesByAccountIdLoadBalancerId(Integer accountId, Integer loadBalancerId) throws EntityNotFoundException { Set<Node> nodes = nodeRepository.getAllNodesByAccountIdLoadBalancerId(accountId, loadBalancerId); return nodes; } @Override @Transactional public Node getNodeByAccountIdLoadBalancerIdNodeId(Integer aid, Integer lid, Integer nid) throws EntityNotFoundException, DeletedStatusException { Node node; node = nodeRepository.getNodeByAccountIdLoadBalancerIdNodeId(loadBalancerRepository.getByIdAndAccountId(lid, aid), nid); return node; } @Override @Transactional public Node getNodeByLoadBalancerIdIpAddressAndPort(Integer loadBalancerId, String ipAddress, Integer ipPort) throws EntityNotFoundException { return nodeRepository.getNodeByLoadBalancerIdIpAddressAndPort(loadBalancerId, ipAddress, ipPort); } @Transactional @DeadLockRetry @Override public LoadBalancer delNodes(LoadBalancer lb, Collection<Node> nodes) throws EntityNotFoundException { return nodeRepository.delNodes(loadBalancerRepository.getById(lb.getId()), nodes); } @Transactional @DeadLockRetry @Override public void delNode(LoadBalancer lb, int nid) throws EntityNotFoundException { nodeRepository.delNode(loadBalancerRepository.getById(lb.getId()), nid); } @Transactional @Override public List<Node> getNodesByIds(Collection<Integer> ids) { List<Node> nodes = nodeRepository.getNodesByIds(ids); return nodes; } @Override @Transactional public Set<Node> createNodes(LoadBalancer newNodesLb) throws EntityNotFoundException, ImmutableEntityException, UnprocessableEntityException, BadRequestException, LimitReachedException { LoadBalancer oldNodesLb = loadBalancerRepository.getByIdAndAccountId(newNodesLb.getId(), newNodesLb.getAccountId()); isLbActive(oldNodesLb); Integer potentialTotalNumNodes = oldNodesLb.getNodes().size() + newNodesLb.getNodes().size(); Integer nodeLimit = accountLimitService.getLimit(oldNodesLb.getAccountId(), AccountLimitType.NODE_LIMIT); if (potentialTotalNumNodes > nodeLimit) { throw new LimitReachedException(String.format("Nodes must not exceed %d per load balancer.", nodeLimit)); } NodesPrioritiesContainer npc = new NodesPrioritiesContainer(oldNodesLb.getNodes(), newNodesLb.getNodes()); // You are not allowed to add secondary nodes with out Some form of monitoring. B-16407 if (npc.hasSecondary() && !loadBalancerRepository.loadBalancerHasHealthMonitor(oldNodesLb.getId())) { throw new BadRequestException(Constants.NoMonitorForSecNodes); } // No secondary nodes unless there are primary nodes if (npc.hasSecondary() && !npc.hasPrimary()) { throw new BadRequestException(Constants.NoPrimaryNodeError); } LOG.debug("Verifying that there are no duplicate nodes..."); if (detectDuplicateNodes(oldNodesLb, newNodesLb)) { LOG.warn("Duplicate nodes found! Sending failure response back to client..."); throw new UnprocessableEntityException("Duplicate nodes detected. One or more nodes already configured on load balancer."); } if (!areAddressesValidForUse(newNodesLb.getNodes(), oldNodesLb)) { LOG.warn("Internal Ips found! Sending failure response back to client..."); throw new BadRequestException("Invalid node address. The load balancer's virtual ip or host end point address cannot be used as a node address."); } try { Node badNode = blackListedItemNode(newNodesLb.getNodes()); if (badNode != null) { throw new BadRequestException(String.format("Invalid node address. The address '%s' is currently not accepted for this request.", badNode.getIpAddress())); } } catch (IPStringConversionException ipe) { LOG.warn("IPStringConversionException thrown. Sending error response to client..."); throw new BadRequestException("IP address was not converted properly, we are unable to process this request."); } catch (IpTypeMissMatchException ipte) { LOG.warn("EntityNotFoundException thrown. Sending error response to client..."); throw new BadRequestException("IP addresses type are mismatched, we are unable to process this request."); } LOG.debug("Updating the lb status to pending_update"); oldNodesLb.setStatus(LoadBalancerStatus.PENDING_UPDATE); //Set status record loadBalancerStatusHistoryService.save(oldNodesLb.getAccountId(), oldNodesLb.getId(), LoadBalancerStatus.PENDING_UPDATE); LOG.debug("Current number of nodes for loadbalancer: " + oldNodesLb.getNodes().size()); LOG.debug("Number of new nodes to be added: " + newNodesLb.getNodes().size()); NodesHelper.setNodesToStatus(newNodesLb, NodeStatus.ONLINE); for (Node newNode : newNodesLb.getNodes()) { if (newNode.getWeight() == null) { newNode.setWeight(1); } } return nodeRepository.addNodes(oldNodesLb, newNodesLb.getNodes()); } @Override @Transactional public LoadBalancer updateNode(LoadBalancer msgLb) throws EntityNotFoundException, ImmutableEntityException, UnprocessableEntityException, BadRequestException { LoadBalancer oldLbNodes = loadBalancerRepository.getByIdAndAccountId(msgLb.getId(), msgLb.getAccountId()); //Prevent hibernate flushing updated object on failure... loadBalancerRepository.detach(oldLbNodes); Node nodeToUpdate = msgLb.getNodes().iterator().next(); if (!loadBalancerContainsNode(oldLbNodes, nodeToUpdate)) { LOG.warn("Node to update not found. Sending response to client..."); throw new EntityNotFoundException(String.format("Node with id #%d not found for loadbalancer #%d", nodeToUpdate.getId(), msgLb.getId())); } isLbActive(oldLbNodes); LOG.debug("Nodes on dbLoadbalancer: " + oldLbNodes.getNodes().size()); for (Node n : oldLbNodes.getNodes()) { if (n.getId().equals(nodeToUpdate.getId())) { LOG.info("Node to be updated found: " + n.getId()); if (nodeToUpdate.getType() != null) { n.setType(nodeToUpdate.getType()); } if (nodeToUpdate.getCondition() != null) { n.setCondition(nodeToUpdate.getCondition()); } if (nodeToUpdate.getIpAddress() != null) { n.setIpAddress(nodeToUpdate.getIpAddress()); } if (nodeToUpdate.getPort() != null) { n.setPort(nodeToUpdate.getPort()); } if (nodeToUpdate.getStatus() != null) { n.setStatus(nodeToUpdate.getStatus()); } if (nodeToUpdate.getWeight() != null) { n.setWeight(nodeToUpdate.getWeight()); } if (nodeToUpdate.getType() != null) { n.setType(nodeToUpdate.getType()); } n.setToBeUpdated(true); break; } } // Won't delete secondary nodes untill you also delete Health Monitor NodesPrioritiesContainer npc = new NodesPrioritiesContainer(oldLbNodes.getNodes()); if (npc.hasSecondary() && oldLbNodes.getHealthMonitor() == null) { throw new BadRequestException(Constants.NoMonitorForSecNodes); } // No secondary nodes unless there are primary nodes if (npc.hasSecondary() && !npc.hasPrimary()) { throw new BadRequestException(Constants.NoPrimaryNodeError); } LOG.debug("Updating the lb status to pending_update"); oldLbNodes.setStatus(LoadBalancerStatus.PENDING_UPDATE); oldLbNodes.setUserName(msgLb.getUserName()); nodeRepository.update(oldLbNodes); //Set status record loadBalancerStatusHistoryService.save(oldLbNodes.getAccountId(), oldLbNodes.getId(), LoadBalancerStatus.PENDING_UPDATE); return oldLbNodes; } @Override @Transactional public void updateNodeStatus(Node node) { nodeRepository.setNodeStatus(node); } @Override @DeadLockRetry @Transactional() public LoadBalancer deleteNode(LoadBalancer loadBalancer) throws EntityNotFoundException, ImmutableEntityException, UnprocessableEntityException { LoadBalancer dbLoadBalancer = loadBalancerRepository.getByIdAndAccountId(loadBalancer.getId(), loadBalancer.getAccountId()); Node nodeToDelete = loadBalancer.getNodes().iterator().next(); if (!loadBalancerContainsNode(dbLoadBalancer, nodeToDelete)) { LOG.warn("Node to delete not found. Sending response to client..."); throw new EntityNotFoundException(String.format("Node with id #%d not found for loadbalancer #%d", nodeToDelete.getId(), loadBalancer.getId())); } String message = StringHelper.immutableLoadBalancer(dbLoadBalancer); if (!loadBalancerRepository.testAndSetStatus(dbLoadBalancer.getAccountId(), dbLoadBalancer.getId(), LoadBalancerStatus.PENDING_UPDATE, false)) { LOG.warn(message); throw new ImmutableEntityException(message); } else { //Set status record loadBalancerStatusHistoryService.save(dbLoadBalancer.getAccountId(), dbLoadBalancer.getId(), LoadBalancerStatus.PENDING_UPDATE); } return loadBalancer; } @Override public boolean detectDuplicateNodes(LoadBalancer dbLoadBalancer, LoadBalancer queueLb) { Set<String> ipAddressesAndPorts = new HashSet<String>(); Boolean retVal = false; String string; IPv6 ip; for (Node dbNode : dbLoadBalancer.getNodes()) { ip = new IPv6(dbNode.getIpAddress()); try { IPUtils.isValidIpv6String(ip.expand()); string = ip.expand() + ":" + dbNode.getPort(); } catch (IPStringConversionException ex) { string = dbNode.getIpAddress() + ":" + dbNode.getPort(); } ipAddressesAndPorts.add(string); } for (Node queueNode : queueLb.getNodes()) { ip = new IPv6(queueNode.getIpAddress()); try { IPUtils.isValidIpv6String(ip.expand()); string = (ip.expand() + ":" + queueNode.getPort()); } catch (IPStringConversionException ex) { string = queueNode.getIpAddress() + ":" + queueNode.getPort(); } if (!ipAddressesAndPorts.add(string)) { retVal = true; } } return retVal; } @Override public boolean areAddressesValidForUse(Set<Node> nodes, LoadBalancer dbLb) { for (LoadBalancerJoinVip loadBalancerJoinVip : dbLb.getLoadBalancerJoinVipSet()) { for (Node node : nodes) { if (loadBalancerJoinVip.getVirtualIp().getIpAddress().equals(node.getIpAddress())) { return false; } } } return true; } @Override public boolean nodeToDeleteIsNotLastActive(LoadBalancer lb, Node deleteNode) { List<Node> nodeList = new ArrayList<Node>(); Node nNode = new Node(); for (Node tNode : lb.getNodes()) { if (NodeCondition.ENABLED.equals(tNode.getCondition())) { nodeList.add(tNode); } if (tNode.getId().equals(deleteNode.getId())) { nNode.setCondition(tNode.getCondition()); } } boolean isFalse; isFalse = true; if (NodeCondition.ENABLED.equals(nNode.getCondition())) { if (!(nodeList.size() <= 1)) { return true; } isFalse = false; } return isFalse; } private static boolean activeNodeCheck(LoadBalancer dbLb, Node n) { List<Node> nodeList = new ArrayList<Node>(); Node updateNode = new Node(); for (Node node : dbLb.getNodes()) { if (NodeCondition.ENABLED.equals(node.getCondition())) { nodeList.add(node); if (node.getId().equals(n.getId())) { updateNode.setCondition(node.getCondition()); } } } if (nodeList.size() <= 1) { if (updateNode.getCondition() != null) { if (!NodeCondition.ENABLED.equals(n.getCondition())) { return false; } } } return true; } private boolean loadBalancerContainsNode(LoadBalancer lb, Node node) { for (Node n : lb.getNodes()) { if (n.getId().equals(node.getId())) { return true; } } return false; } @Override public NodeMap getNodeMap(Integer accountId, Integer loadbalancerId) { return nodeRepository.getNodeMap(accountId, loadbalancerId); } @Override @Transactional public List<String> prepareForNodesDeletion(Integer accountId, Integer loadBalancerId, List<Integer> ids) throws EntityNotFoundException { List<String> validationErrors = new ArrayList<String>(); String format; String errMsg; LoadBalancer dlb = new LoadBalancer(); dlb.setId(loadBalancerId); NodeMap nodeMap = getNodeMap(accountId, loadBalancerId); Set<Integer> idSet = NodeMap.listToSet(ids); Set<Integer> notMyIds = nodeMap.idsThatAreNotInThisMap(idSet); // Either some one elese ids or non existent ids List<Node> doomedNodes = nodeMap.getNodesList(idSet); int doomedNodeCount = doomedNodes.size(); int batch_delete_limit = accountLimitService.getLimit(accountId, AccountLimitType.BATCH_DELETE_LIMIT); if (doomedNodeCount > batch_delete_limit) { format = "Request to delete %d nodes exceeds the account limit" + " BATCH_DELETE_LIMIT of %d please attempt to delete fewer then %d nodes"; errMsg = String.format(format, doomedNodeCount, batch_delete_limit, batch_delete_limit); validationErrors.add(errMsg); } //Set status record loadBalancerStatusHistoryService.save(accountId, loadBalancerId, LoadBalancerStatus.PENDING_UPDATE); if (notMyIds.size() > 0) { // Don't even take this request seriously any // ID does not belong to this account format = "Node ids %s are not a part of your loadbalancer"; errMsg = String.format(format, StringConverter.integersAsString(notMyIds)); validationErrors.add(errMsg); } return validationErrors; } }